diff --git a/application/pom.xml b/application/pom.xml index 303f206601..7aa0c54594 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -65,6 +65,10 @@ org.thingsboard.rule-engine rule-engine-api + + org.thingsboard.common + cluster-api + org.thingsboard.rule-engine rule-engine-components @@ -289,6 +293,11 @@ junit test + + org.awaitility + awaitility + test + org.mockito mockito-core diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 130c0c6dd5..03a311fe8c 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -18,8 +18,8 @@ "resources": [], "templateHtml": "
", "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n", - "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"requestPersistent\"\n ]\n}", + "controllerScript": "var requestTimeout = 500;\nvar requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var spaceIndex = localCommand.indexOf(' ');\n if (spaceIndex === -1 && !localCommand.length) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n } else {\n var params;\n if (spaceIndex === -1) {\n spaceIndex = localCommand.length;\n }\n var name = localCommand.substr(0, spaceIndex);\n var args = localCommand.substr(spaceIndex + 1);\n if (args.length) {\n try {\n params = JSON.parse(args);\n } catch (e) {\n params = args;\n }\n }\n performRpc(this, name, params, requestUUID);\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1:]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2:]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": 2, \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestPersistent, persistentPollingInterval, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } @@ -55,7 +55,7 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"minValue\": {\n \"title\": \"Minimum value\",\n \"type\": \"number\",\n \"default\": 0\n },\n \"maxValue\": {\n \"title\": \"Maximum value\",\n \"type\": \"number\",\n \"default\": 100\n },\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"number\",\n \"default\": 50\n },\n \"title\": {\n \"title\": \"Knob title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"getValueMethod\": {\n \"title\": \"Get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"Set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"minValue\", \"maxValue\", \"getValueMethod\", \"setValueMethod\", \"requestTimeout\"]\n },\n \"form\": [\n \"minValue\",\n \"maxValue\",\n \"initialValue\",\n \"title\",\n \"getValueMethod\",\n \"setValueMethod\",\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"maxValue\":100,\"initialValue\":50,\"minValue\":0,\"title\":\"Knob control\",\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\"},\"title\":\"Knob Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } @@ -73,7 +73,7 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showOnOffLabels\": {\n \"title\": \"Show on/off labels\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n \"showOnOffLabels\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"showOnOffLabels\":true,\"title\":\"Switch control\"},\"title\":\"Switch Control\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } @@ -91,7 +91,7 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"Switch title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve on/off value using method\",\n \"type\": \"string\",\n \"default\": \"rpc\"\n },\n \"valueKey\": {\n \"title\": \"Attribute/Timeseries value key (only when subscribe for attribute/timeseries method)\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"getValueMethod\": {\n \"title\": \"RPC get value method\",\n \"type\": \"string\",\n \"default\": \"getValue\"\n },\n \"setValueMethod\": {\n \"title\": \"RPC set value method\",\n \"type\": \"string\",\n \"default\": \"setValue\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"convertValueFunction\": {\n \"title\": \"Convert value function, f(value), returns payload used by RPC set value method\",\n \"type\": \"string\",\n \"default\": \"return value;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"none\",\n \"label\": \"Don't retrieve\"\n },\n {\n \"value\": \"rpc\",\n \"label\": \"Call RPC get value method\"\n },\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueKey\",\n \"getValueMethod\",\n \"setValueMethod\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"convertValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":false,\"getValueMethod\":\"getValue\",\"setValueMethod\":\"setValue\",\"title\":\"Round switch\",\"retrieveValueMethod\":\"rpc\",\"valueKey\":\"value\",\"parseValueFunction\":\"return data ? true : false;\",\"convertValueFunction\":\"return value;\"},\"title\":\"Round switch\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } @@ -109,7 +109,7 @@ "templateHtml": "", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onResize = function() {\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"initialValue\": {\n \"title\": \"Initial value\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"title\": {\n \"title\": \"LED title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"ledColor\": {\n \"title\": \"LED Color\",\n \"type\": \"string\",\n \"default\": \"green\"\n },\n \"performCheckStatus\": {\n \"title\": \"Perform RPC device status check\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"checkStatusMethod\": {\n \"title\": \"RPC check device status method\",\n \"type\": \"string\",\n \"default\": \"checkStatus\"\n },\n \"retrieveValueMethod\": {\n \"title\": \"Retrieve led status value using method\",\n \"type\": \"string\",\n \"default\": \"attribute\"\n },\n \"valueAttribute\": {\n \"title\": \"Device attribute/timeseries containing led status value\",\n \"type\": \"string\",\n \"default\": \"value\"\n },\n \"parseValueFunction\": {\n \"title\": \"Parse led status value function, f(data), returns boolean\",\n \"type\": \"string\",\n \"default\": \"return data ? true : false;\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n }\n },\n \"required\": [\"valueAttribute\", \"requestTimeout\"]\n },\n \"form\": [\n \"initialValue\",\n \"title\",\n {\n \"key\": \"ledColor\",\n \"type\": \"color\"\n },\n \"performCheckStatus\",\n \"checkStatusMethod\",\n {\n \"key\": \"retrieveValueMethod\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"attribute\",\n \"label\": \"Subscribe for attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Subscribe for timeseries\"\n }\n ]\n },\n \"valueAttribute\",\n {\n \"key\": \"parseValueFunction\",\n \"type\": \"javascript\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":500,\"initialValue\":true,\"title\":\"Led indicator\",\"ledColor\":\"#4caf50\",\"valueAttribute\":\"value\",\"retrieveValueMethod\":\"attribute\",\"parseValueFunction\":\"return data ? true : false;\",\"performCheckStatus\":true,\"checkStatusMethod\":\"checkStatus\"},\"title\":\"Led indicator\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{},\"decimals\":2}" } @@ -126,8 +126,8 @@ "resources": [], "templateHtml": "
\n
\n {{title}}\n
\n
\n
\n \n
\n
\n
\n {{ error }}\n
\n
", "templateCss": ".tb-rpc-button {\n width: 100%;\n height: 100%;\n}\n\n.tb-rpc-button .title-container {\n font-weight: 500;\n white-space: nowrap;\n margin: 10px 0;\n}\n\n.tb-rpc-button .button-container div{\n min-width: 80%\n}\n\n.tb-rpc-button .button-container .mat-button{\n width: 100%;\n margin: 0;\n}\n\n.tb-rpc-button .error-container {\n position: absolute;\n top: 2%;\n right: 0;\n left: 0;\n z-index: 4;\n height: 14px;\n}\n\n.tb-rpc-button .error-container .button-error {\n color: #ff3315;\n white-space: nowrap;\n}", - "controllerScript": "self.onInit = function() {\n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", + "controllerScript": "var requestPersistent = false;\nvar persistentPollingInterval = 5000;\n\nself.onInit = function() {\n if (self.ctx.settings.requestPersistent) {\n requestPersistent = self.ctx.settings.requestPersistent;\n }\n if (self.ctx.settings.persistentPollingInterval) {\n persistentPollingInterval = self.ctx.settings.persistentPollingInterval;\n }\n \n self.ctx.ngZone.run(function() {\n init(); \n self.ctx.detectChanges();\n });\n};\n\nfunction init() {\n let rpcEnabled = self.ctx.defaultSubscription.rpcEnabled;\n\n self.ctx.$scope.buttonLable = self.ctx.settings.buttonText;\n self.ctx.$scope.showTitle = self.ctx.settings.title &&\n self.ctx.settings.title.length ? true : false;\n self.ctx.$scope.title = self.ctx.settings.title;\n self.ctx.$scope.styleButton = self.ctx.settings.styleButton;\n\n if (self.ctx.settings.styleButton.isPrimary ===\n false) {\n self.ctx.$scope.customStyle = {\n 'background-color': self.ctx.$scope.styleButton.bgColor,\n 'color': self.ctx.$scope.styleButton.textColor\n };\n }\n\n if (!rpcEnabled) {\n self.ctx.$scope.error =\n 'Target device is not set!';\n }\n\n self.ctx.$scope.sendCommand = function() {\n var rpcMethod = self.ctx.settings.methodName;\n var rpcParams = self.ctx.settings.methodParams;\n var timeout = self.ctx.settings.requestTimeout;\n var oneWayElseTwoWay = self.ctx.settings.oneWayElseTwoWay ?\n true : false;\n\n var commandPromise;\n if (oneWayElseTwoWay) {\n commandPromise = self.ctx.controlApi.sendOneWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n } else {\n commandPromise = self.ctx.controlApi.sendTwoWayCommand(\n rpcMethod, rpcParams, timeout, requestPersistent, persistentPollingInterval);\n }\n commandPromise.subscribe(\n function success() {\n self.ctx.$scope.error = \"\";\n self.ctx.detectChanges();\n },\n function fail(rejection) {\n if (self.ctx.settings.showError) {\n self.ctx.$scope.error =\n rejection.status + \": \" +\n rejection.statusText;\n self.ctx.detectChanges();\n }\n }\n );\n };\n}\n\nself.onDestroy = function() {\n self.ctx.controlApi.completedCommand();\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"title\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"buttonText\": {\n \"title\": \"Button label\",\n \"type\": \"string\",\n \"default\": \"Send RPC\"\n },\n \"oneWayElseTwoWay\": {\n \"title\": \"Is One Way Command\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showError\": {\n \"title\": \"Show RPC command execution error\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"methodName\": {\n \"title\": \"RPC method\",\n \"type\": \"string\",\n \"default\": \"rpcCommand\"\n },\n \"methodParams\": {\n \"title\": \"RPC method params\",\n \"type\": \"string\",\n \"default\": \"{}\"\n },\n \"requestTimeout\": {\n \"title\": \"RPC request timeout\",\n \"type\": \"number\",\n \"default\": 5000\n },\n \"requestPersistent\": {\n \"title\": \"RPC request persistent\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"persistentPollingInterval\": {\n \"title\": \"Polling interval in milliseconds to get persistent RPC command response\",\n \"type\": \"number\",\n \"default\": 5000,\n \"minimum\": 1000\n },\n \"styleButton\": {\n \"type\": \"object\",\n \"title\": \"Button Style\",\n \"properties\": {\n \"isRaised\": {\n \"type\": \"boolean\",\n \"title\": \"Raised\",\n \"default\": true\n },\n \"isPrimary\": {\n \"type\": \"boolean\",\n \"title\": \"Primary color\",\n \"default\": false\n },\n \"bgColor\": {\n \"type\": \"string\",\n \"title\": \"Button background color\",\n \"default\": null\n },\n \"textColor\": {\n \"type\": \"string\",\n \"title\": \"Button text color\",\n \"default\": null\n }\n }\n },\n \"required\": []\n }\n },\n \"form\": [\n \"title\",\n \"buttonText\",\n \"oneWayElseTwoWay\",\n \"showError\",\n \"methodName\",\n {\n \"key\": \"methodParams\",\n \"type\": \"json\"\n },\n \"requestTimeout\",\n \"requestPersistent\",\n {\n \"key\": \"persistentPollingInterval\",\n \"condition\": \"model.requestPersistent === true\"\n },\n {\n \"key\": \"styleButton\",\n \"items\": [\n \"styleButton.isRaised\",\n \"styleButton.isPrimary\",\n {\n \"key\": \"styleButton.bgColor\",\n \"type\": \"color\"\n },\n {\n \"key\": \"styleButton.textColor\",\n \"type\": \"color\"\n }\n ]\n }\n ]\n\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":false,\"backgroundColor\":\"#e6e7e8\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"requestTimeout\":5000,\"oneWayElseTwoWay\":true,\"buttonText\":\"Send RPC\",\"styleButton\":{\"isRaised\":true,\"isPrimary\":false},\"methodName\":\"rpcCommand\",\"methodParams\":\"{}\"},\"title\":\"RPC Button\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index 50f538d5dc..f0077287e9 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -209,6 +209,7 @@ CREATE TABLE IF NOT EXISTS rpc ( expiration_time bigint NOT NULL, request varchar(10000000) NOT NULL, response varchar(10000000), + additional_info varchar(10000000), status varchar(255) NOT NULL ); diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 7ad3d912ac..5516762783 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -81,7 +81,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 81e9c8917a..223fb94937 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -55,7 +55,7 @@ public class AppActor extends ContextAwareActor { private final TbTenantProfileCache tenantProfileCache; private final TenantService tenantService; private final Set deletedTenants; - private boolean ruleChainsInitialized; + private volatile boolean ruleChainsInitialized; private AppActor(ActorSystemContext systemContext) { super(systemContext); @@ -69,7 +69,7 @@ public class AppActor extends ContextAwareActor { if (!ruleChainsInitialized) { initTenantActors(); ruleChainsInitialized = true; - if (msg.getMsgType() != MsgType.APP_INIT_MSG) { + if (msg.getMsgType() != MsgType.APP_INIT_MSG && msg.getMsgType() != MsgType.PARTITION_CHANGE_MSG) { log.warn("Rule Chains initialized by unexpected message: {}", msg); } } @@ -95,6 +95,7 @@ public class AppActor extends ContextAwareActor { case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG: case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: + case REMOVE_RPC_TO_DEVICE_ACTOR_MSG: onToDeviceActorMsg((TenantAwareMsg) msg, true); break; case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG: diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java index 3e06b8387c..47bc554532 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; +import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; @@ -84,6 +85,9 @@ public class DeviceActor extends ContextAwareActor { case DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG: processor.processEdgeUpdate((DeviceEdgeUpdateMsg) msg); break; + case REMOVE_RPC_TO_DEVICE_ACTOR_MSG: + processor.processRemoveRpc(ctx, (RemoveRpcActorMsg) msg); + break; default: return false; } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index e91e55bacb..6224a3635d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -26,7 +26,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.LinkedHashMapRemoveEldest; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg; @@ -86,8 +86,9 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; +import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; @@ -97,11 +98,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; @@ -236,6 +235,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { rpc.setExpirationTime(request.getExpirationTime()); rpc.setRequest(JacksonUtil.valueToTree(request)); rpc.setStatus(status); + rpc.setAdditionalInfo(JacksonUtil.valueToTree(request.getAdditionalInfo())); return systemContext.getTbRpcService().save(tenantId, rpc); } @@ -264,6 +264,21 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } } + void processRemoveRpc(TbActorCtx context, RemoveRpcActorMsg msg) { + log.debug("[{}] Processing remove rpc command", msg.getRequestId()); + Integer requestId = null; + for (Map.Entry entry : toDeviceRpcPendingMap.entrySet()) { + if (entry.getValue().getMsg().getMsg().getId().equals(msg.getRequestId())) { + requestId = entry.getKey(); + break; + } + } + + if (requestId != null) { + toDeviceRpcPendingMap.remove(requestId); + } + } + private void registerPendingRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) { toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout); @@ -383,11 +398,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } private void reportSessionOpen() { - systemContext.getDeviceStateService().onDeviceConnect(deviceId); + systemContext.getDeviceStateService().onDeviceConnect(tenantId, deviceId); } private void reportSessionClose() { - systemContext.getDeviceStateService().onDeviceDisconnect(deviceId); + systemContext.getDeviceStateService().onDeviceDisconnect(tenantId, deviceId); } private void handleGetAttributesRequest(TbActorCtx context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) { @@ -586,13 +601,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); return; } - log.info("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size()); + log.debug("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size()); sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId()))); if (sessions.size() == 1) { reportSessionOpen(); } - systemContext.getDeviceStateService().onDeviceActivity(deviceId, System.currentTimeMillis()); + systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); dumpSessions(); } else if (msg.getEvent() == SessionEvent.CLOSED) { log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); @@ -622,7 +637,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { if (subscriptionInfo.getRpcSubscription()) { rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo()); } - systemContext.getDeviceStateService().onDeviceActivity(deviceId, subscriptionInfo.getLastActivityTime()); + systemContext.getDeviceStateService().onDeviceActivity(tenantId, deviceId, subscriptionInfo.getLastActivityTime()); dumpSessions(); } @@ -729,18 +744,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { edgeEvent.setBody(body); edgeEvent.setEdgeId(edgeId); - ListenableFuture future = systemContext.getEdgeEventService().saveAsync(edgeEvent); - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(EdgeEvent result) { - systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); - } - - @Override - public void onFailure(Throwable t) { - log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t); - } - }, systemContext.getDbCallbackExecutor()); + systemContext.getEdgeEventService().save(edgeEvent); + systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); } private List toTsKvProtos(@Nullable List result) { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 9a1afb9ff8..740ff0102d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -453,6 +454,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getDeviceService(); } + @Override + public TbClusterService getClusterService() { + return mainCtx.getClusterService(); + } + @Override public DashboardService getDashboardService() { return mainCtx.getDashboardService(); diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index 10d6272beb..cb916f9baa 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -49,7 +49,7 @@ import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper; import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import java.util.ArrayList; import java.util.Collections; diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index 1a74e368f5..c995aadcae 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -165,6 +165,7 @@ public class TenantActor extends RuleChainManagerActor { case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG: case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: + case REMOVE_RPC_TO_DEVICE_ACTOR_MSG: onToDeviceActorMsg((DeviceAwareMsg) msg, true); break; case RULE_CHAIN_TO_RULE_CHAIN_MSG: diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/controller/RpcController.java rename to application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java index 50ab5c7863..43b6af1626 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AbstractRpcController.java @@ -16,41 +16,28 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.rpc.Rpc; -import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.LocalRequestMetaData; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.security.AccessValidator; @@ -59,97 +46,32 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.telemetry.exception.ToErrorResponseEntity; import javax.annotation.Nullable; -import java.io.IOException; import java.util.Optional; import java.util.UUID; /** * Created by ashvayka on 22.03.18. */ -@RestController @TbCoreComponent -@RequestMapping(TbUrlConstants.RPC_URL_PREFIX) @Slf4j -public class RpcController extends BaseController { - - protected final ObjectMapper jsonMapper = new ObjectMapper(); +public abstract class AbstractRpcController extends BaseController { @Autowired - private TbCoreDeviceRpcService deviceRpcService; + protected TbCoreDeviceRpcService deviceRpcService; @Autowired private AccessValidator accessValidator; @Value("${server.rest.server_side_rpc.min_timeout:5000}") - private long minTimeout; + protected long minTimeout; @Value("${server.rest.server_side_rpc.default_timeout:10000}") - private long defaultTimeout; - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) - @ResponseBody - public DeferredResult handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { - return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody); - } - - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST) - @ResponseBody - public DeferredResult handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { - return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody); - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET) - @ResponseBody - public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException { - checkParameter("RpcId", strRpc); - try { - RpcId rpcId = new RpcId(UUID.fromString(strRpc)); - return checkRpcId(rpcId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET) - @ResponseBody - public PageData getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId, - @RequestParam int pageSize, - @RequestParam int page, - @RequestParam RpcStatus rpcStatus, - @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String sortProperty, - @RequestParam(required = false) String sortOrder) throws ThingsboardException { - checkParameter("DeviceId", strDeviceId); - try { - TenantId tenantId = getCurrentUser().getTenantId(); - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId)); - return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink)); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE) - @ResponseBody - public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException { - checkParameter("RpcId", strRpc); - try { - rpcService.deleteRpc(getTenantId(), new RpcId(UUID.fromString(strRpc))); - } catch (Exception e) { - throw handleException(e); - } - } + protected long defaultTimeout; - private DeferredResult handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { + protected DeferredResult handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody, HttpStatus timeoutStatus, HttpStatus noActiveConnectionStatus) throws ThingsboardException { try { - JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), jsonMapper.writeValueAsString(rpcRequestBody.get("params"))); + JsonNode rpcRequestBody = JacksonUtil.toJsonNode(requestBody); + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(rpcRequestBody.get("method").asText(), JacksonUtil.toString(rpcRequestBody.get("params"))); SecurityUser currentUser = getCurrentUser(); TenantId tenantId = currentUser.getTenantId(); final DeferredResult response = new DeferredResult<>(); @@ -157,7 +79,8 @@ public class RpcController extends BaseController { long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout); UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID(); boolean persisted = rpcRequestBody.has(DataConstants.PERSISTENT) && rpcRequestBody.get(DataConstants.PERSISTENT).asBoolean(); - accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback>() { + String additionalInfo = JacksonUtil.toString(rpcRequestBody.get(DataConstants.ADDITIONAL_INFO)); + accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<>() { @Override public void onSuccess(@Nullable DeferredResult result) { ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(rpcRequestUUID, @@ -166,9 +89,10 @@ public class RpcController extends BaseController { oneWay, expTime, body, - persisted + persisted, + additionalInfo ); - deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse), currentUser); + deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse, timeoutStatus, noActiveConnectionStatus), currentUser); } @Override @@ -184,12 +108,12 @@ public class RpcController extends BaseController { } })); return response; - } catch (IOException ioe) { + } catch (IllegalArgumentException ioe) { throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS); } } - public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) { + public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response, HttpStatus timeoutStatus, HttpStatus noActiveConnectionStatus) { Optional rpcError = response.getError(); DeferredResult responseWriter = rpcRequest.getResponseWriter(); if (rpcError.isPresent()) { @@ -197,13 +121,13 @@ public class RpcController extends BaseController { RpcError error = rpcError.get(); switch (error) { case TIMEOUT: - responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); + responseWriter.setResult(new ResponseEntity<>(timeoutStatus)); break; case NO_ACTIVE_CONNECTION: - responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT)); + responseWriter.setResult(new ResponseEntity<>(noActiveConnectionStatus)); break; default: - responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); + responseWriter.setResult(new ResponseEntity<>(timeoutStatus)); break; } } else { @@ -212,8 +136,8 @@ public class RpcController extends BaseController { String data = responseData.get(); try { logRpcCall(rpcRequest, rpcError, null); - responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); - } catch (IOException e) { + responseWriter.setResult(new ResponseEntity<>(JacksonUtil.toJsonNode(data), HttpStatus.OK)); + } catch (IllegalArgumentException e) { log.debug("Failed to decode device response: {}", data, e); logRpcCall(rpcRequest, rpcError, e); responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 5bd099d07a..d8406088c7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -26,12 +26,12 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.SmsService; -import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.UpdateMessage; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.model.SecuritySettings; +import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -67,7 +67,7 @@ public class AdminController extends BaseController { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key)); if (adminSettings.getKey().equals("mail")) { - ((ObjectNode) adminSettings.getJsonValue()).put("password", ""); + ((ObjectNode) adminSettings.getJsonValue()).remove("password"); } return adminSettings; } catch (Exception e) { @@ -84,7 +84,7 @@ public class AdminController extends BaseController { adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); if (adminSettings.getKey().equals("mail")) { mailService.updateMailConfiguration(); - ((ObjectNode) adminSettings.getJsonValue()).put("password", ""); + ((ObjectNode) adminSettings.getJsonValue()).remove("password"); } else if (adminSettings.getKey().equals("sms")) { smsService.updateSmsConfiguration(); } @@ -126,6 +126,10 @@ public class AdminController extends BaseController { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); adminSettings = checkNotNull(adminSettings); if (adminSettings.getKey().equals("mail")) { + if(!adminSettings.getJsonValue().has("password")) { + AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail")); + ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText()); + } String email = getCurrentUser().getEmail(); mailService.sendTestMail(adminSettings.getJsonValue(), email); } diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index f0af59c7bc..7bbc84fb32 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.page.PageData; @@ -46,6 +47,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.util.List; + @RestController @TbCoreComponent @RequestMapping("/api") @@ -112,10 +115,13 @@ public class AlarmController extends BaseController { AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + List relatedEdgeIds = findRelatedEdgeIds(getTenantId(), alarm.getOriginator()); + logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_DELETE, null); - sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED); + + sendAlarmDeleteNotificationMsg(getTenantId(), alarmId, relatedEdgeIds, alarm); return alarmService.deleteAlarm(getTenantId(), alarmId); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 36e2bcc92a..c251d07d2e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -33,7 +33,6 @@ import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; @@ -118,7 +117,6 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -129,7 +127,7 @@ import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; @@ -846,13 +844,25 @@ public abstract class BaseController { } protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds) { + sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null); + } + + protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, String body) { if (edgeIds != null && !edgeIds.isEmpty()) { for (EdgeId edgeId : edgeIds) { - sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, null, EdgeEventActionType.DELETED); + sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, null, EdgeEventActionType.DELETED); } } } + protected void sendAlarmDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, Alarm alarm) { + try { + sendDeleteNotificationMsg(tenantId, entityId, edgeIds, json.writeValueAsString(alarm)); + } catch (Exception e) { + log.warn("Failed to push delete alarm msg to core: {}", alarm, e); + } + } + protected void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) { try { sendNotificationMsgToEdgeService(tenantId, null, entityId, json.writeValueAsString(customerId), null, action); @@ -870,42 +880,7 @@ public abstract class BaseController { } private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { - if (!edgesEnabled) { - return; - } - if (type == null) { - if (entityId != null) { - type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType()); - } else { - log.trace("[{}] entity id and type are null. Ignoring this notification", tenantId); - return; - } - if (type == null) { - log.trace("[{}] edge event type is null. Ignoring this notification [{}]", tenantId, entityId); - return; - } - } - TransportProtos.EdgeNotificationMsgProto.Builder builder = TransportProtos.EdgeNotificationMsgProto.newBuilder(); - builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); - builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - builder.setType(type.name()); - builder.setAction(action.name()); - if (entityId != null) { - builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); - builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); - builder.setEntityType(entityId.getEntityType().name()); - } - if (edgeId != null) { - builder.setEdgeIdMSB(edgeId.getId().getMostSignificantBits()); - builder.setEdgeIdLSB(edgeId.getId().getLeastSignificantBits()); - } - if (body != null) { - builder.setBody(body); - } - TransportProtos.EdgeNotificationMsgProto msg = builder.build(); - log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg); - tbClusterService.pushMsgToCore(tenantId, entityId != null ? entityId : tenantId, - TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null); + tbClusterService.sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, type, action); } protected List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 9eb1dc044a..3e58e0656e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -149,7 +149,7 @@ public class CustomerController extends BaseController { ActionType.DELETED, null, strCustomerId); sendDeleteNotificationMsg(getTenantId(), customerId, relatedEdgeIds); - tbClusterService.onEntityStateChange(getTenantId(), customerId, ComponentLifecycleEvent.DELETED); + tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), customerId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { logEntityAction(emptyId(EntityType.CUSTOMER), diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 904171976a..8cfa5ee2cf 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -36,7 +36,6 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg; -import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; @@ -60,7 +59,6 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -134,20 +132,16 @@ public class DeviceController extends BaseController { try { device.setTenantId(getCurrentUser().getTenantId()); - checkEntity(device.getId(), device, Resource.DEVICE); - - Device oldDevice; + Device oldDevice = null; if (!created) { - oldDevice = deviceService.findDeviceById(getTenantId(), device.getId()); + oldDevice = checkDeviceId(device.getId(), Operation.WRITE); } else { - oldDevice = null; + checkEntity(null, device, Resource.DEVICE); } Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); - onDeviceCreatedOrUpdated(savedDevice, !created); - - otaPackageStateService.update(savedDevice, oldDevice); + onDeviceCreatedOrUpdated(savedDevice, oldDevice, !created); return savedDevice; } catch (Exception e) { @@ -158,29 +152,16 @@ public class DeviceController extends BaseController { } - private void onDeviceCreatedOrUpdated(Device device, boolean updated) { - tbClusterService.onDeviceChange(device, null); - tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(device.getTenantId(), - device.getId(), device.getName(), device.getType()), null); - tbClusterService.onEntityStateChange(device.getTenantId(), device.getId(), updated ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); - - if (updated) { - sendEntityNotificationMsg(device.getTenantId(), device.getId(), EdgeEventActionType.UPDATED); - } + private void onDeviceCreatedOrUpdated(Device savedDevice, Device oldDevice, boolean updated) { + tbClusterService.onDeviceUpdated(savedDevice, oldDevice); try { - logEntityAction(device.getId(), device, - device.getCustomerId(), + logEntityAction(savedDevice.getId(), savedDevice, + savedDevice.getCustomerId(), updated ? ActionType.UPDATED : ActionType.ADDED, null); } catch (ThingsboardException e) { log.error("Failed to log entity action", e); } - - if (updated) { - deviceStateService.onDeviceUpdated(device); - } else { - deviceStateService.onDeviceAdded(device); - } } @PreAuthorize("hasAuthority('TENANT_ADMIN')") @@ -197,15 +178,12 @@ public class DeviceController extends BaseController { deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId); tbClusterService.onDeviceDeleted(device, null); - tbClusterService.onEntityStateChange(device.getTenantId(), deviceId, ComponentLifecycleEvent.DELETED); logEntityAction(deviceId, device, device.getCustomerId(), ActionType.DELETED, null, strDeviceId); sendDeleteNotificationMsg(getTenantId(), deviceId, relatedEdgeIds); - - deviceStateService.onDeviceDeleted(device); } catch (Exception e) { logEntityAction(emptyId(EntityType.DEVICE), null, @@ -819,8 +797,7 @@ public class DeviceController extends BaseController { @PostMapping("/device/bulk_import") public BulkImportResult processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception { return deviceBulkImportService.processBulkImport(request, getCurrentUser(), importedDeviceInfo -> { - onDeviceCreatedOrUpdated(importedDeviceInfo.getEntity(), importedDeviceInfo.isUpdated()); - otaPackageStateService.update(importedDeviceInfo.getEntity(), importedDeviceInfo.getOldEntity()); + onDeviceCreatedOrUpdated(importedDeviceInfo.getEntity(), importedDeviceInfo.getOldEntity(), importedDeviceInfo.isUpdated()); }); } diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index ceee45147b..3a68551773 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -161,7 +161,7 @@ public class DeviceProfileController extends BaseController { DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); - tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), savedDeviceProfile.getId(), + tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), savedDeviceProfile.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); logEntityAction(savedDeviceProfile.getId(), savedDeviceProfile, @@ -191,7 +191,7 @@ public class DeviceProfileController extends BaseController { deviceProfileService.deleteDeviceProfile(getTenantId(), deviceProfileId); tbClusterService.onDeviceProfileDelete(deviceProfile, null); - tbClusterService.onEntityStateChange(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED); + tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), deviceProfile.getId(), ComponentLifecycleEvent.DELETED); logEntityAction(deviceProfileId, deviceProfile, null, diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 2b1b6611d7..e48afd1bff 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -153,7 +153,7 @@ public class EdgeController extends BaseController { edgeService.assignDefaultRuleChainsToEdge(tenantId, edge.getId()); } - tbClusterService.onEntityStateChange(edge.getTenantId(), edge.getId(), + tbClusterService.broadcastEntityStateChangeEvent(edge.getTenantId(), edge.getId(), updated ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); logEntityAction(edge.getId(), edge, null, updated ? ActionType.UPDATED : ActionType.ADDED, null); @@ -169,7 +169,7 @@ public class EdgeController extends BaseController { Edge edge = checkEdgeId(edgeId, Operation.DELETE); edgeService.deleteEdge(getTenantId(), edgeId); - tbClusterService.onEntityStateChange(getTenantId(), edgeId, + tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, ComponentLifecycleEvent.DELETED); logEntityAction(edgeId, edge, @@ -220,7 +220,7 @@ public class EdgeController extends BaseController { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, customerId)); - tbClusterService.onEntityStateChange(getTenantId(), edgeId, + tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, ComponentLifecycleEvent.UPDATED); logEntityAction(edgeId, savedEdge, @@ -254,7 +254,7 @@ public class EdgeController extends BaseController { Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(getCurrentUser().getTenantId(), edgeId)); - tbClusterService.onEntityStateChange(getTenantId(), edgeId, + tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, ComponentLifecycleEvent.UPDATED); logEntityAction(edgeId, edge, @@ -284,7 +284,7 @@ public class EdgeController extends BaseController { Customer publicCustomer = customerService.findOrCreatePublicCustomer(edge.getTenantId()); Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, publicCustomer.getId())); - tbClusterService.onEntityStateChange(getTenantId(), edgeId, + tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, ComponentLifecycleEvent.UPDATED); logEntityAction(edgeId, savedEdge, @@ -376,7 +376,7 @@ public class EdgeController extends BaseController { Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId); - tbClusterService.onEntityStateChange(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED); + tbClusterService.broadcastEntityStateChangeEvent(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED); logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null); diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java index d94d26fc87..64537d9f5f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -24,13 +24,11 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Resource; @@ -66,22 +64,11 @@ public class Lwm2mController extends BaseController { checkEntity(device.getId(), device, Resource.DEVICE); Device savedDevice = deviceService.saveDeviceWithCredentials(device, credentials); checkNotNull(savedDevice); - - tbClusterService.onDeviceChange(savedDevice, null); - tbClusterService.pushMsgToCore(new DeviceNameOrTypeUpdateMsg(savedDevice.getTenantId(), - savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null); - tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), - device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - + tbClusterService.onDeviceUpdated(savedDevice, device); logEntityAction(savedDevice.getId(), savedDevice, savedDevice.getCustomerId(), device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); - if (device.getId() == null) { - deviceStateService.onDeviceAdded(savedDevice); - } else { - deviceStateService.onDeviceUpdated(savedDevice); - } return savedDevice; } catch (Exception e) { logEntityAction(emptyId(EntityType.DEVICE), device, diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java new file mode 100644 index 0000000000..9bece05110 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV1Controller.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.util.UUID; + +@RestController +@TbCoreComponent +@RequestMapping(TbUrlConstants.RPC_V1_URL_PREFIX) +@Slf4j +public class RpcV1Controller extends AbstractRpcController { + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) + @ResponseBody + public DeferredResult handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { + return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT); + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST) + @ResponseBody + public DeferredResult handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { + return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.REQUEST_TIMEOUT, HttpStatus.CONFLICT); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java new file mode 100644 index 0000000000..ae9081c39a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/RpcV2Controller.java @@ -0,0 +1,128 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RpcId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.rpc.RemoveRpcActorMsg; +import org.thingsboard.server.service.security.permission.Operation; + +import java.util.UUID; + +import static org.thingsboard.server.common.data.DataConstants.RPC_DELETED; + +@RestController +@TbCoreComponent +@RequestMapping(TbUrlConstants.RPC_V2_URL_PREFIX) +@Slf4j +public class RpcV2Controller extends AbstractRpcController { + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) + @ResponseBody + public DeferredResult handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { + return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT); + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST) + @ResponseBody + public DeferredResult handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { + return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody, HttpStatus.GATEWAY_TIMEOUT, HttpStatus.GATEWAY_TIMEOUT); + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.GET) + @ResponseBody + public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException { + checkParameter("RpcId", strRpc); + try { + RpcId rpcId = new RpcId(UUID.fromString(strRpc)); + return checkRpcId(rpcId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/persistent/device/{deviceId}", method = RequestMethod.GET) + @ResponseBody + public PageData getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam RpcStatus rpcStatus, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + checkParameter("DeviceId", strDeviceId); + try { + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId)); + return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/persistent/{rpcId}", method = RequestMethod.DELETE) + @ResponseBody + public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException { + checkParameter("RpcId", strRpc); + try { + RpcId rpcId = new RpcId(UUID.fromString(strRpc)); + Rpc rpc = checkRpcId(rpcId, Operation.DELETE); + + if (rpc != null) { + if (rpc.getStatus().equals(RpcStatus.QUEUED)) { + RemoveRpcActorMsg removeMsg = new RemoveRpcActorMsg(getTenantId(), rpc.getDeviceId(), rpc.getUuidId()); + log.trace("[{}] Forwarding msg {} to queue actor!", rpc.getDeviceId(), rpc); + tbClusterService.pushMsgToCore(removeMsg, null); + } + + rpcService.deleteRpc(getTenantId(), rpcId); + + TbMsg msg = TbMsg.newMsg(RPC_DELETED, rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); + tbClusterService.pushMsgToRuleEngine(getTenantId(), rpc.getDeviceId(), msg, null); + } + } catch (Exception e) { + throw handleException(e); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 7b6821d63d..a71fb70e0a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -149,7 +149,7 @@ public class RuleChainController extends BaseController { RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); if (RuleChainType.CORE.equals(savedRuleChain.getType())) { - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(), + tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), savedRuleChain.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); } @@ -183,7 +183,7 @@ public class RuleChainController extends BaseController { RuleChain savedRuleChain = installScripts.createDefaultRuleChain(getCurrentUser().getTenantId(), request.getName()); - tbClusterService.onEntityStateChange(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); + tbClusterService.broadcastEntityStateChangeEvent(savedRuleChain.getTenantId(), savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); logEntityAction(savedRuleChain.getId(), savedRuleChain, null, ActionType.ADDED, null); @@ -210,7 +210,7 @@ public class RuleChainController extends BaseController { if (previousRootRuleChain != null) { previousRootRuleChain = ruleChainService.findRuleChainById(getTenantId(), previousRootRuleChain.getId()); - tbClusterService.onEntityStateChange(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), + tbClusterService.broadcastEntityStateChangeEvent(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), ComponentLifecycleEvent.UPDATED); logEntityAction(previousRootRuleChain.getId(), previousRootRuleChain, @@ -218,7 +218,7 @@ public class RuleChainController extends BaseController { } ruleChain = ruleChainService.findRuleChainById(getTenantId(), ruleChainId); - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), + tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); logEntityAction(ruleChain.getId(), ruleChain, @@ -254,7 +254,7 @@ public class RuleChainController extends BaseController { RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId())); if (RuleChainType.CORE.equals(ruleChain.getType())) { - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); + tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); } logEntityAction(ruleChain.getId(), ruleChain, @@ -323,9 +323,9 @@ public class RuleChainController extends BaseController { if (RuleChainType.CORE.equals(ruleChain.getType())) { referencingRuleChainIds.forEach(referencingRuleChainId -> - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); + tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED)); - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED); + tbClusterService.broadcastEntityStateChangeEvent(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED); } logEntityAction(ruleChainId, ruleChain, @@ -456,7 +456,7 @@ public class RuleChainController extends BaseController { List importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, RuleChainType.CORE, overwrite); if (!CollectionUtils.isEmpty(importResults)) { for (RuleChainImportResult importResult : importResults) { - tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent()); + tbClusterService.broadcastEntityStateChangeEvent(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent()); } } } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java b/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java index 0ba1a87971..4b5e52a594 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbUrlConstants.java @@ -20,5 +20,6 @@ package org.thingsboard.server.controller; */ public class TbUrlConstants { public static final String TELEMETRY_URL_PREFIX = "/api/plugins/telemetry"; - public static final String RPC_URL_PREFIX = "/api/plugins/rpc"; + public static final String RPC_V1_URL_PREFIX = "/api/plugins/rpc"; + public static final String RPC_V2_URL_PREFIX = "/api/rpc"; } diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 04d37371e3..f8ac4acf49 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -99,7 +99,7 @@ public class TenantController extends BaseController { } tenantProfileCache.evict(tenant.getId()); tbClusterService.onTenantChange(tenant, null); - tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), newTenant ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenant; } catch (Exception e) { @@ -118,7 +118,7 @@ public class TenantController extends BaseController { tenantService.deleteTenant(tenantId); tenantProfileCache.evict(tenantId); tbClusterService.onTenantDelete(tenant, null); - tbClusterService.onEntityStateChange(tenantId, tenantId, ComponentLifecycleEvent.DELETED); + tbClusterService.broadcastEntityStateChangeEvent(tenantId, tenantId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index fc0a1b0291..e201c8f815 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -98,7 +97,7 @@ public class TenantProfileController extends BaseController { tenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(getTenantId(), tenantProfile)); tenantProfileCache.put(tenantProfile); tbClusterService.onTenantProfileChange(tenantProfile, null); - tbClusterService.onEntityStateChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), + tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, tenantProfile.getId(), newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenantProfile; } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index ce151ea8cb..27aef2d42e 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -41,7 +41,7 @@ import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import java.util.List; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 123537194a..47f698baff 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -61,7 +61,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index 7b3c98c445..dc00c63fd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -29,6 +29,7 @@ import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -69,6 +70,8 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { private static final String CLAIM_DATA_ATTRIBUTE_NAME = "claimingData"; private static final ObjectMapper mapper = new ObjectMapper(); + @Autowired + private TbClusterService clusterService; @Autowired private DeviceService deviceService; @Autowired @@ -155,6 +158,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { device.setCustomerId(customerId); Device savedDevice = deviceService.saveDevice(device); + clusterService.onDeviceUpdated(savedDevice, device); return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor()); } return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor()); @@ -179,13 +183,14 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { cacheEviction(device.getId()); Customer unassignedCustomer = customerService.findCustomerById(tenantId, device.getCustomerId()); device.setCustomerId(null); - deviceService.saveDevice(device); + Device savedDevice = deviceService.saveDevice(device); + clusterService.onDeviceUpdated(savedDevice, device); if (isAllowedClaimingByDefault) { return Futures.immediateFuture(new ReclaimResult(unassignedCustomer)); } SettableFuture result = SettableFuture.create(); telemetryService.saveAndNotify( - tenantId, device.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList( + tenantId, savedDevice.getId(), DataConstants.SERVER_SCOPE, Collections.singletonList( new BaseAttributeKvEntry(new BooleanDataEntry(CLAIM_ATTRIBUTE_NAME, true), System.currentTimeMillis()) ), new FutureCallback<>() { @@ -198,7 +203,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { public void onFailure(Throwable t) { result.setException(t); } - }); + }); return result; } cacheEviction(device.getId()); @@ -238,7 +243,7 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { public void onFailure(Throwable t) { result.setException(t); } - }); + }); return result; } diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java index 36f4ea06af..8b8cfe3131 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceProvisionServiceImpl.java @@ -24,6 +24,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -77,6 +78,9 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private static final String DEVICE_PROVISION_STATE = "provisionState"; private static final String PROVISIONED_STATE = "provisioned"; + @Autowired + TbClusterService clusterService; + @Autowired DeviceDao deviceDao; @@ -190,8 +194,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { provisionRequest.setDeviceName(newDeviceName); } Device savedDevice = deviceService.saveDevice(provisionRequest, profile); - - deviceStateService.onDeviceAdded(savedDevice); + clusterService.onDeviceUpdated(savedDevice, null); saveProvisionStateAttribute(savedDevice).get(); pushDeviceCreatedEventToRuleEngine(savedDevice); notify(savedDevice, provisionRequest, DataConstants.PROVISION_SUCCESS, true); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index c861207f2d..cb24a57fb2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -16,11 +16,7 @@ package org.thingsboard.server.service.edge; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.edge.Edge; @@ -41,8 +37,7 @@ import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; -import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -65,9 +60,6 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { @Autowired private TbClusterService clusterService; - @Autowired - private DbCallbackExecutorService dbCallbackExecutorService; - @Autowired private EdgeProcessor edgeProcessor; @@ -123,23 +115,13 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { edgeEvent.setEntityId(entityId.getId()); } edgeEvent.setBody(body); - ListenableFuture future = edgeEventService.saveAsync(edgeEvent); - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(@Nullable EdgeEvent result) { - clusterService.onEdgeEventUpdate(tenantId, edgeId); - } - - @Override - public void onFailure(Throwable t) { - log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t); - } - }, dbCallbackExecutorService); - + edgeEventService.save(edgeEvent); + clusterService.onEdgeEventUpdate(tenantId, edgeId); } @Override public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { + log.trace("Pushing notification to edge {}", edgeNotificationMsg); try { TenantId tenantId = new TenantId(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 32e23fddad..46b36ca99f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -259,7 +259,7 @@ public final class EdgeGrpcSession implements Closeable { log.error("[{}] Msg processing failed! Error msg: {}", edge.getRoutingKey(), msg.getErrorMsg()); } if (sessionState.getPendingMsgsMap().isEmpty()) { - log.debug("[{}] Pending msgs map is empty. Stopping current iteration {}", edge.getRoutingKey(), msg); + log.debug("[{}] Pending msgs map is empty. Stopping current iteration", edge.getRoutingKey()); if (sessionState.getScheduledSendDownlinkTask() != null) { sessionState.getScheduledSendDownlinkTask().cancel(false); } @@ -527,7 +527,7 @@ public final class EdgeGrpcSession implements Closeable { case RULE_CHAIN_METADATA: return ctx.getRuleChainProcessor().processRuleChainMetadataToEdge(edgeEvent, msgType); case ALARM: - return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType); + return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType, action); case USER: return ctx.getUserProcessor().processUserToEdge(edge, edgeEvent, msgType, action); case RELATION: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeProtoUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeProtoUtils.java new file mode 100644 index 0000000000..a9dfac4df7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeProtoUtils.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc; + +import com.google.protobuf.BoolValue; +import com.google.protobuf.ByteString; +import com.google.protobuf.BytesValue; +import com.google.protobuf.Int64Value; +import com.google.protobuf.StringValue; + +public class EdgeProtoUtils { + + private EdgeProtoUtils() { + } + + public static BoolValue getBoolValue(Boolean value) { + BoolValue.Builder builder = BoolValue.newBuilder(); + builder.setValue(value); + return builder.build(); + } + + public static StringValue getStringValue(String value) { + StringValue.Builder builder = StringValue.newBuilder(); + builder.setValue(value); + return builder.build(); + } + + public static Int64Value getInt64Value(Long value) { + Int64Value.Builder builder = Int64Value.newBuilder(); + builder.setValue(value); + return builder.build(); + } + + public static BytesValue getBytesValue(ByteString value) { + BytesValue.Builder builder = BytesValue.newBuilder(); + builder.setValue(value); + return builder.build(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java index c1fc9f0585..039280a76c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java @@ -58,6 +58,8 @@ public class AlarmMsgConstructor { } AlarmUpdateMsg.Builder builder = AlarmUpdateMsg.newBuilder() .setMsgType(msgType) + .setIdMSB(alarm.getId().getId().getMostSignificantBits()) + .setIdLSB(alarm.getId().getId().getLeastSignificantBits()) .setName(alarm.getName()) .setType(alarm.getType()) .setOriginatorName(entityName) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java index a835660dae..c5438f9445 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java @@ -24,6 +24,9 @@ import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getInt64Value; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class AssetMsgConstructor { @@ -36,14 +39,14 @@ public class AssetMsgConstructor { .setName(asset.getName()) .setType(asset.getType()); if (asset.getLabel() != null) { - builder.setLabel(asset.getLabel()); + builder.setLabel(getStringValue(asset.getLabel())); } if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + builder.setCustomerIdMSB(getInt64Value(customerId.getId().getMostSignificantBits())); + builder.setCustomerIdLSB(getInt64Value(customerId.getId().getLeastSignificantBits())); } if (asset.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(asset.getAdditionalInfo())); + builder.setAdditionalInfo(getStringValue(JacksonUtil.toString(asset.getAdditionalInfo()))); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java index c5161a674b..5e9a861995 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/CustomerMsgConstructor.java @@ -23,6 +23,8 @@ import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class CustomerMsgConstructor { @@ -34,31 +36,31 @@ public class CustomerMsgConstructor { .setIdLSB(customer.getId().getId().getLeastSignificantBits()) .setTitle(customer.getTitle()); if (customer.getCountry() != null) { - builder.setCountry(customer.getCountry()); + builder.setCountry(getStringValue(customer.getCountry())); } if (customer.getState() != null) { - builder.setState(customer.getState()); + builder.setState(getStringValue(customer.getState())); } if (customer.getCity() != null) { - builder.setCity(customer.getCity()); + builder.setCity(getStringValue(customer.getCity())); } if (customer.getAddress() != null) { - builder.setAddress(customer.getAddress()); + builder.setAddress(getStringValue(customer.getAddress())); } if (customer.getAddress2() != null) { - builder.setAddress2(customer.getAddress2()); + builder.setAddress2(getStringValue(customer.getAddress2())); } if (customer.getZip() != null) { - builder.setZip(customer.getZip()); + builder.setZip(getStringValue(customer.getZip())); } if (customer.getPhone() != null) { - builder.setPhone(customer.getPhone()); + builder.setPhone(getStringValue(customer.getPhone())); } if (customer.getEmail() != null) { - builder.setEmail(customer.getEmail()); + builder.setEmail(getStringValue(customer.getEmail())); } if (customer.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(customer.getAdditionalInfo())); + builder.setAdditionalInfo(getStringValue(JacksonUtil.toString(customer.getAdditionalInfo()))); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java index f3f8573aa2..b916d10aa8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getInt64Value; + @Component @TbCoreComponent public class DashboardMsgConstructor { @@ -36,8 +38,8 @@ public class DashboardMsgConstructor { .setTitle(dashboard.getTitle()) .setConfiguration(JacksonUtil.toString(dashboard.getConfiguration())); if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + builder.setCustomerIdMSB(getInt64Value(customerId.getId().getMostSignificantBits())); + builder.setCustomerIdLSB(getInt64Value(customerId.getId().getLeastSignificantBits())); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java index a28e80d80a..1506e2f4e8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java @@ -32,6 +32,9 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.UUID; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getInt64Value; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class DeviceMsgConstructor { @@ -46,21 +49,21 @@ public class DeviceMsgConstructor { .setName(device.getName()) .setType(device.getType()); if (device.getLabel() != null) { - builder.setLabel(device.getLabel()); + builder.setLabel(getStringValue(device.getLabel())); } if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + builder.setCustomerIdMSB(getInt64Value(customerId.getId().getMostSignificantBits())); + builder.setCustomerIdLSB(getInt64Value(customerId.getId().getLeastSignificantBits())); } if (device.getDeviceProfileId() != null) { - builder.setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()); - builder.setDeviceProfileIdLSB(device.getDeviceProfileId().getId().getLeastSignificantBits()); + builder.setDeviceProfileIdMSB(getInt64Value(device.getDeviceProfileId().getId().getMostSignificantBits())); + builder.setDeviceProfileIdLSB(getInt64Value(device.getDeviceProfileId().getId().getLeastSignificantBits())); } if (device.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(device.getAdditionalInfo())); + builder.setAdditionalInfo(getStringValue(JacksonUtil.toString(device.getAdditionalInfo()))); } if (conflictName != null) { - builder.setConflictName(conflictName); + builder.setConflictName(getStringValue(conflictName)); } return builder.build(); } @@ -74,7 +77,7 @@ public class DeviceMsgConstructor { .setCredentialsId(deviceCredentials.getCredentialsId()); } if (deviceCredentials.getCredentialsValue() != null) { - builder.setCredentialsValue(deviceCredentials.getCredentialsValue()); + builder.setCredentialsValue(getStringValue(deviceCredentials.getCredentialsValue())); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java index 965b7c9e67..8646fc3756 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java @@ -27,6 +27,9 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import java.nio.charset.StandardCharsets; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getBytesValue; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class DeviceProfileMsgConstructor { @@ -52,19 +55,19 @@ public class DeviceProfileMsgConstructor { // builder.setDefaultQueueName(deviceProfile.getDefaultQueueName()); // } if (deviceProfile.getDescription() != null) { - builder.setDescription(deviceProfile.getDescription()); + builder.setDescription(getStringValue(deviceProfile.getDescription())); } if (deviceProfile.getTransportType() != null) { - builder.setTransportType(deviceProfile.getTransportType().name()); + builder.setTransportType(getStringValue(deviceProfile.getTransportType().name())); } if (deviceProfile.getProvisionType() != null) { - builder.setProvisionType(deviceProfile.getProvisionType().name()); + builder.setProvisionType(getStringValue(deviceProfile.getProvisionType().name())); } if (deviceProfile.getProvisionDeviceKey() != null) { - builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey()); + builder.setProvisionDeviceKey(getStringValue(deviceProfile.getProvisionDeviceKey())); } if (deviceProfile.getImage() != null) { - builder.setImage(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(getBytesValue(ByteString.copyFrom(deviceProfile.getImage().getBytes(StandardCharsets.UTF_8)))); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java index 6b6447e1e7..4f9d580ecb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java @@ -25,6 +25,9 @@ import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getInt64Value; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class EntityViewMsgConstructor { @@ -51,11 +54,11 @@ public class EntityViewMsgConstructor { .setEntityIdLSB(entityView.getEntityId().getId().getLeastSignificantBits()) .setEntityType(entityType); if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + builder.setCustomerIdMSB(getInt64Value(customerId.getId().getMostSignificantBits())); + builder.setCustomerIdLSB(getInt64Value(customerId.getId().getLeastSignificantBits())); } if (entityView.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(entityView.getAdditionalInfo())); + builder.setAdditionalInfo(getStringValue(JacksonUtil.toString(entityView.getAdditionalInfo()))); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java index b4763e3f78..839f19dad5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RelationMsgConstructor.java @@ -22,6 +22,8 @@ import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class RelationMsgConstructor { @@ -35,10 +37,12 @@ public class RelationMsgConstructor { .setToIdMSB(entityRelation.getTo().getId().getMostSignificantBits()) .setToIdLSB(entityRelation.getTo().getId().getLeastSignificantBits()) .setToEntityType(entityRelation.getTo().getEntityType().name()) - .setType(entityRelation.getType()) - .setAdditionalInfo(JacksonUtil.toString(entityRelation.getAdditionalInfo())); + .setType(entityRelation.getType()); + if (entityRelation.getAdditionalInfo() != null) { + builder.setAdditionalInfo(JacksonUtil.toString(entityRelation.getAdditionalInfo())); + } if (entityRelation.getTypeGroup() != null) { - builder.setTypeGroup(entityRelation.getTypeGroup().name()); + builder.setTypeGroup(getStringValue(entityRelation.getTypeGroup().name())); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java index 8438057a73..f137fda04e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java @@ -37,6 +37,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.ArrayList; import java.util.List; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getInt64Value; + @Component @Slf4j @TbCoreComponent @@ -54,8 +56,8 @@ public class RuleChainMsgConstructor { .setDebugMode(ruleChain.isDebugMode()) .setConfiguration(JacksonUtil.toString(ruleChain.getConfiguration())); if (ruleChain.getFirstRuleNodeId() != null) { - builder.setFirstRuleNodeIdMSB(ruleChain.getFirstRuleNodeId().getId().getMostSignificantBits()) - .setFirstRuleNodeIdLSB(ruleChain.getFirstRuleNodeId().getId().getLeastSignificantBits()); + builder.setFirstRuleNodeIdMSB(getInt64Value(ruleChain.getFirstRuleNodeId().getId().getMostSignificantBits())) + .setFirstRuleNodeIdLSB(getInt64Value(ruleChain.getFirstRuleNodeId().getId().getLeastSignificantBits())); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java index af422cc8ab..118bd4e6dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java @@ -16,16 +16,19 @@ package org.thingsboard.server.service.edge.rpc.constructor; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getInt64Value; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class UserMsgConstructor { @@ -38,20 +41,17 @@ public class UserMsgConstructor { .setEmail(user.getEmail()) .setAuthority(user.getAuthority().name()); if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + builder.setCustomerIdMSB(getInt64Value(customerId.getId().getMostSignificantBits())); + builder.setCustomerIdLSB(getInt64Value(customerId.getId().getLeastSignificantBits())); } if (user.getFirstName() != null) { - builder.setFirstName(user.getFirstName()); + builder.setFirstName(getStringValue(user.getFirstName())); } if (user.getLastName() != null) { - builder.setLastName(user.getLastName()); - } - if (user.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(user.getAdditionalInfo())); + builder.setLastName(getStringValue(user.getLastName())); } if (user.getAdditionalInfo() != null) { - builder.setAdditionalInfo(JacksonUtil.toString(user.getAdditionalInfo())); + builder.setAdditionalInfo(getStringValue(JacksonUtil.toString(user.getAdditionalInfo()))); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java index 6d8799f8fe..8cf5c2e792 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java @@ -16,14 +16,16 @@ package org.thingsboard.server.service.edge.rpc.constructor; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetType; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class WidgetTypeMsgConstructor { @@ -33,21 +35,21 @@ public class WidgetTypeMsgConstructor { .setMsgType(msgType) .setIdMSB(widgetType.getId().getId().getMostSignificantBits()) .setIdLSB(widgetType.getId().getId().getLeastSignificantBits()); - if (widgetType.getBundleAlias() != null) { - builder.setBundleAlias(widgetType.getBundleAlias()); - } - if (widgetType.getAlias() != null) { - builder.setAlias(widgetType.getAlias()); - } - if (widgetType.getName() != null) { - builder.setName(widgetType.getName()); - } - if (widgetType.getDescriptor() != null) { - builder.setDescriptorJson(JacksonUtil.toString(widgetType.getDescriptor())); - } - if (widgetType.getTenantId().equals(TenantId.SYS_TENANT_ID)) { - builder.setIsSystem(true); - } + if (widgetType.getBundleAlias() != null) { + builder.setBundleAlias(getStringValue(widgetType.getBundleAlias())); + } + if (widgetType.getAlias() != null) { + builder.setAlias(getStringValue(widgetType.getAlias())); + } + if (widgetType.getName() != null) { + builder.setName(getStringValue(widgetType.getName())); + } + if (widgetType.getDescriptor() != null) { + builder.setDescriptorJson(getStringValue(JacksonUtil.toString(widgetType.getDescriptor()))); + } + if (widgetType.getTenantId().equals(TenantId.SYS_TENANT_ID)) { + builder.setIsSystem(true); + } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java index 7a2a9d00cd..56464ad8a5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetsBundleMsgConstructor.java @@ -26,6 +26,9 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import java.nio.charset.StandardCharsets; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getBytesValue; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; + @Component @TbCoreComponent public class WidgetsBundleMsgConstructor { @@ -38,10 +41,10 @@ public class WidgetsBundleMsgConstructor { .setTitle(widgetsBundle.getTitle()) .setAlias(widgetsBundle.getAlias()); if (widgetsBundle.getImage() != null) { - builder.setImage(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8))); + builder.setImage(getBytesValue(ByteString.copyFrom(widgetsBundle.getImage().getBytes(StandardCharsets.UTF_8)))); } if (widgetsBundle.getDescription() != null) { - builder.setDescription(widgetsBundle.getDescription()); + builder.setDescription(getStringValue(widgetsBundle.getDescription())); } if (widgetsBundle.getTenantId().equals(TenantId.SYS_TENANT_ID)) { builder.setIsSystem(true); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java index c2e5dc2e3e..90d30737f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -54,6 +55,7 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { EntityId originatorId = getAlarmOriginator(tenantId, alarmUpdateMsg.getOriginatorName(), EntityType.valueOf(alarmUpdateMsg.getOriginatorType())); if (originatorId == null) { + log.warn("Originator not found for the alarm msg {}", alarmUpdateMsg); return Futures.immediateFuture(null); } try { @@ -113,59 +115,84 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { } } - public DownlinkMsg processAlarmToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType) { + public DownlinkMsg processAlarmToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + AlarmId alarmId = new AlarmId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - try { - AlarmId alarmId = new AlarmId(edgeEvent.getEntityId()); - Alarm alarm = alarmService.findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId).get(); - if (alarm != null) { + switch (action) { + case ADDED: + case UPDATED: + case ALARM_ACK: + case ALARM_CLEAR: + try { + Alarm alarm = alarmService.findAlarmByIdAsync(edgeEvent.getTenantId(), alarmId).get(); + if (alarm != null) { + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm)) + .build(); + } + } catch (Exception e) { + log.error("Can't process alarm msg [{}] [{}]", edgeEvent, msgType, e); + } + break; + case DELETED: + Alarm alarm = mapper.convertValue(edgeEvent.getBody(), Alarm.class); + AlarmUpdateMsg alarmUpdateMsg = + alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm)) + .addAlarmUpdateMsg(alarmUpdateMsg) .build(); - } - } catch (Exception e) { - log.error("Can't process alarm msg [{}] [{}]", edgeEvent, msgType, e); + break; } return downlinkMsg; } - public void processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + public void processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); - Futures.addCallback(alarmFuture, new FutureCallback() { - @Override - public void onSuccess(@Nullable Alarm alarm) { - if (alarm != null) { - EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); - if (type != null) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId edgeId : pageData.getData()) { - saveEdgeEvent(tenantId, - edgeId, - EdgeEventType.ALARM, - EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), - alarmId, - null); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } + switch (actionType) { + case DELETED: + EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); + Alarm alarm = mapper.readValue(edgeNotificationMsg.getBody(), Alarm.class); + saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, mapper.valueToTree(alarm)); + break; + default: + ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); + Futures.addCallback(alarmFuture, new FutureCallback() { + @Override + public void onSuccess(@Nullable Alarm alarm) { + if (alarm != null) { + EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); + if (type != null) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId edgeId : pageData.getData()) { + saveEdgeEvent(tenantId, + edgeId, + EdgeEventType.ALARM, + EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), + alarmId, + null); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); } - } while (pageData != null && pageData.hasNext()); + } } - } - } - @Override - public void onFailure(Throwable t) { - log.warn("[{}] can't find alarm by id [{}] {}", tenantId.getId(), alarmId.getId(), t); - } - }, dbCallbackExecutorService); + @Override + public void onFailure(Throwable t) { + log.warn("[{}] can't find alarm by id [{}] {}", tenantId.getId(), alarmId.getId(), t); + } + }, dbCallbackExecutorService); + } } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index f32576499e..c5cbc39b46 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -17,11 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.edge.Edge; @@ -66,7 +62,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstruc import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; @Slf4j @@ -178,12 +174,12 @@ public abstract class BaseEdgeProcessor { @Autowired protected DbCallbackExecutorService dbCallbackExecutorService; - protected ListenableFuture saveEdgeEvent(TenantId tenantId, - EdgeId edgeId, - EdgeEventType type, - EdgeEventActionType action, - EntityId entityId, - JsonNode body) { + protected void saveEdgeEvent(TenantId tenantId, + EdgeId edgeId, + EdgeEventType type, + EdgeEventActionType action, + EntityId entityId, + JsonNode body) { log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], " + "action [{}], entityId [{}], body [{}]", tenantId, edgeId, type, action, entityId, body); @@ -197,19 +193,8 @@ public abstract class BaseEdgeProcessor { edgeEvent.setEntityId(entityId.getId()); } edgeEvent.setBody(body); - ListenableFuture future = edgeEventService.saveAsync(edgeEvent); - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(@Nullable EdgeEvent result) { - tbClusterService.onEdgeEventUpdate(tenantId, edgeId); - } - - @Override - public void onFailure(Throwable t) { - log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t); - } - }, dbCallbackExecutorService); - return future; + edgeEventService.save(edgeEvent); + tbClusterService.onEdgeEventUpdate(tenantId, edgeId); } protected CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity, Edge edge) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java index 3e2c740acc..2f24c1a219 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.processor; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; @@ -27,7 +26,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -60,7 +59,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; @@ -105,19 +104,8 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { Device newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); ObjectNode body = mapper.createObjectNode(); body.put("conflictName", deviceName); - ListenableFuture future = - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(EdgeEvent edgeEvent) { - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null); - } - - @Override - public void onFailure(Throwable t) { - log.error("[{}] Failed to save ENTITY_MERGE_REQUEST edge event [{}][{}]", tenantId, deviceUpdateMsg, edge.getId(), t); - } - }, dbCallbackExecutorService); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null); } } while (pageData != null && pageData.hasNext()); } else { @@ -155,7 +143,9 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, device.getId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.valueOf(deviceCredentialsUpdateMsg.getCredentialsType())); deviceCredentials.setCredentialsId(deviceCredentialsUpdateMsg.getCredentialsId()); - deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.getCredentialsValue()); + if (deviceCredentialsUpdateMsg.hasCredentialsValue()) { + deviceCredentials.setCredentialsValue(deviceCredentialsUpdateMsg.getCredentialsValue().getValue()); + } deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials); } catch (Exception e) { log.error("Can't update device credentials for device [{}], deviceCredentialsUpdateMsg [{}]", device.getName(), deviceCredentialsUpdateMsg, e); @@ -173,14 +163,20 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { if (device != null) { device.setName(deviceUpdateMsg.getName()); device.setType(deviceUpdateMsg.getType()); - device.setLabel(deviceUpdateMsg.getLabel()); - device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); - if (deviceUpdateMsg.getDeviceProfileIdMSB() != 0 && deviceUpdateMsg.getDeviceProfileIdLSB() != 0) { + if (deviceUpdateMsg.hasLabel()) { + device.setLabel(deviceUpdateMsg.getLabel().getValue()); + } + if (deviceUpdateMsg.hasAdditionalInfo()) { + device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo().getValue())); + } + if (deviceUpdateMsg.hasDeviceProfileIdMSB() && deviceUpdateMsg.hasDeviceProfileIdLSB()) { DeviceProfileId deviceProfileId = new DeviceProfileId( - new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB())); + new UUID(deviceUpdateMsg.getDeviceProfileIdMSB().getValue(), + deviceUpdateMsg.getDeviceProfileIdLSB().getValue())); device.setDeviceProfileId(deviceProfileId); } - deviceService.saveDevice(device); + Device savedDevice = deviceService.saveDevice(device); + tbClusterService.onDeviceUpdated(savedDevice, device); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); } else { log.warn("[{}] can't find device [{}], edge [{}]", tenantId, deviceUpdateMsg, edge.getId()); @@ -206,22 +202,26 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { device.setCustomerId(getCustomerId(edge)); device.setName(deviceName); device.setType(deviceUpdateMsg.getType()); - device.setLabel(deviceUpdateMsg.getLabel()); - device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo())); - if (deviceUpdateMsg.getDeviceProfileIdMSB() != 0 && deviceUpdateMsg.getDeviceProfileIdLSB() != 0) { + if (deviceUpdateMsg.hasLabel()) { + device.setLabel(deviceUpdateMsg.getLabel().getValue()); + } + if (deviceUpdateMsg.hasAdditionalInfo()) { + device.setAdditionalInfo(JacksonUtil.toJsonNode(deviceUpdateMsg.getAdditionalInfo().getValue())); + } + if (deviceUpdateMsg.hasDeviceProfileIdMSB() && deviceUpdateMsg.hasDeviceProfileIdLSB()) { DeviceProfileId deviceProfileId = new DeviceProfileId( - new UUID(deviceUpdateMsg.getDeviceProfileIdMSB(), deviceUpdateMsg.getDeviceProfileIdLSB())); + new UUID(deviceUpdateMsg.getDeviceProfileIdMSB().getValue(), + deviceUpdateMsg.getDeviceProfileIdLSB().getValue())); device.setDeviceProfileId(deviceProfileId); } Device savedDevice = deviceService.saveDevice(device, false); + tbClusterService.onDeviceUpdated(savedDevice, device); if (created) { DeviceCredentials deviceCredentials = new DeviceCredentials(); deviceCredentials.setDeviceId(new DeviceId(savedDevice.getUuidId())); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsId(org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric(20)); deviceCredentialsService.createDeviceCredentials(device.getTenantId(), deviceCredentials); - - deviceStateService.onDeviceAdded(savedDevice); } createRelationFromEdge(tenantId, edge.getId(), device.getId()); pushDeviceCreatedEventToRuleEngine(tenantId, edge, device); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java index 6318356a47..d23edc0f60 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java @@ -72,7 +72,9 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor { entityRelation.setTo(toId); entityRelation.setType(relationUpdateMsg.getType()); - entityRelation.setTypeGroup(RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup())); + if (relationUpdateMsg.hasTypeGroup()) { + entityRelation.setTypeGroup(RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup().getValue())); + } entityRelation.setAdditionalInfo(mapper.readTree(relationUpdateMsg.getAdditionalInfo())); switch (relationUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java index a1b686cd1a..a1f8fc89ec 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java @@ -73,7 +73,7 @@ import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg; import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import java.util.ArrayList; import java.util.HashMap; @@ -122,26 +122,13 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { @Override public ListenableFuture processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg) { log.trace("[{}] processRuleChainMetadataRequestMsg [{}][{}]", tenantId, edge.getName(), ruleChainMetadataRequestMsg); - SettableFuture futureToSet = SettableFuture.create(); if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() != 0 && ruleChainMetadataRequestMsg.getRuleChainIdLSB() != 0) { RuleChainId ruleChainId = new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB())); - ListenableFuture future = saveEdgeEvent(tenantId, edge.getId(), + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null); - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(@Nullable EdgeEvent result) { - futureToSet.set(null); - } - - @Override - public void onFailure(Throwable t) { - log.error("Can't save edge event [{}]", ruleChainMetadataRequestMsg, t); - futureToSet.setException(t); - } - }, dbCallbackExecutorService); } - return futureToSet; + return Futures.immediateFuture(null); } @Override @@ -154,8 +141,8 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { if (type != null) { SettableFuture futureToSet = SettableFuture.create(); String scope = attributesRequestMsg.getScope(); - ListenableFuture> ssAttrFuture = attributesService.findAll(tenantId, entityId, scope); - Futures.addCallback(ssAttrFuture, new FutureCallback>() { + ListenableFuture> findAttrFuture = attributesService.findAll(tenantId, entityId, scope); + Futures.addCallback(findAttrFuture, new FutureCallback>() { @Override public void onSuccess(@Nullable List ssAttributes) { if (ssAttributes != null && !ssAttributes.isEmpty()) { @@ -184,8 +171,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { entityId, body); } catch (Exception e) { - log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e); - throw new RuntimeException("[" + edge.getName() + "] Failed to send attribute updates to the edge", e); + log.error("[{}] Failed to save attribute updates to the edge", edge.getName(), e); + futureToSet.setException(new RuntimeException("[" + edge.getName() + "] Failed to send attribute updates to the edge", e)); + return; } } else { log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId, @@ -198,7 +186,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { @Override public void onFailure(Throwable t) { - log.error("Can't save attributes [{}]", attributesRequestMsg, t); + log.error("Can't find attributes [{}]", attributesRequestMsg, t); futureToSet.setException(t); } }, dbCallbackExecutorService); @@ -273,82 +261,39 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { @Override public ListenableFuture processDeviceCredentialsRequestMsg(TenantId tenantId, Edge edge, DeviceCredentialsRequestMsg deviceCredentialsRequestMsg) { log.trace("[{}] processDeviceCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), deviceCredentialsRequestMsg); - SettableFuture futureToSet = SettableFuture.create(); if (deviceCredentialsRequestMsg.getDeviceIdMSB() != 0 && deviceCredentialsRequestMsg.getDeviceIdLSB() != 0) { DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsRequestMsg.getDeviceIdMSB(), deviceCredentialsRequestMsg.getDeviceIdLSB())); - ListenableFuture future = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_UPDATED, deviceId, null); - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(@Nullable EdgeEvent result) { - futureToSet.set(null); - } - - @Override - public void onFailure(Throwable t) { - log.error("Can't save edge event [{}]", deviceCredentialsRequestMsg, t); - futureToSet.setException(t); - } - }, dbCallbackExecutorService); } - return futureToSet; + return Futures.immediateFuture(null); } @Override public ListenableFuture processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg) { log.trace("[{}] processUserCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), userCredentialsRequestMsg); - SettableFuture futureToSet = SettableFuture.create(); if (userCredentialsRequestMsg.getUserIdMSB() != 0 && userCredentialsRequestMsg.getUserIdLSB() != 0) { UserId userId = new UserId(new UUID(userCredentialsRequestMsg.getUserIdMSB(), userCredentialsRequestMsg.getUserIdLSB())); - ListenableFuture future = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, EdgeEventActionType.CREDENTIALS_UPDATED, userId, null); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable EdgeEvent result) { - futureToSet.set(null); - } - - @Override - public void onFailure(Throwable t) { - log.error("Can't save edge event [{}]", userCredentialsRequestMsg, t); - futureToSet.setException(t); - } - }, dbCallbackExecutorService); } - return futureToSet; + return Futures.immediateFuture(null); } @Override public ListenableFuture processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg) { log.trace("[{}] processDeviceProfileDevicesRequestMsg [{}][{}]", tenantId, edge.getName(), deviceProfileDevicesRequestMsg); - SettableFuture futureToSet = SettableFuture.create(); if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() != 0 && deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() != 0) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB())); DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); - List> futures; if (deviceProfileById != null) { - futures = syncDevices(tenantId, edge, deviceProfileById.getName()); - } else { - futures = new ArrayList<>(); + syncDevices(tenantId, edge, deviceProfileById.getName()); } - Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() { - @Override - public void onSuccess(@Nullable List result) { - futureToSet.set(null); - } - - @Override - public void onFailure(Throwable t) { - log.error("Can't sync devices by device profile [{}]", deviceProfileDevicesRequestMsg, t); - futureToSet.setException(t); - } - }, dbCallbackExecutorService); } - return futureToSet; + return Futures.immediateFuture(null); } - private List> syncDevices(TenantId tenantId, Edge edge, String deviceType) { - List> futures = new ArrayList<>(); + private void syncDevices(TenantId tenantId, Edge edge, String deviceType) { log.trace("[{}] syncDevices [{}][{}]", tenantId, edge.getName(), deviceType); try { PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); @@ -358,7 +303,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); for (Device device : pageData.getData()) { - futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null)); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null); } if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -368,40 +313,25 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { } catch (Exception e) { log.error("Exception during loading edge device(s) on sync!", e); } - return futures; } @Override public ListenableFuture processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) { log.trace("[{}] processWidgetBundleTypesRequestMsg [{}][{}]", tenantId, edge.getName(), widgetBundleTypesRequestMsg); - SettableFuture futureToSet = SettableFuture.create(); if (widgetBundleTypesRequestMsg.getWidgetBundleIdMSB() != 0 && widgetBundleTypesRequestMsg.getWidgetBundleIdLSB() != 0) { WidgetsBundleId widgetsBundleId = new WidgetsBundleId(new UUID(widgetBundleTypesRequestMsg.getWidgetBundleIdMSB(), widgetBundleTypesRequestMsg.getWidgetBundleIdLSB())); WidgetsBundle widgetsBundleById = widgetsBundleService.findWidgetsBundleById(tenantId, widgetsBundleId); - List> futures = new ArrayList<>(); if (widgetsBundleById != null) { List widgetTypesToPush = widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(widgetsBundleById.getTenantId(), widgetsBundleById.getAlias()); for (WidgetType widgetType : widgetTypesToPush) { - futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null)); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null); } } - Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() { - @Override - public void onSuccess(@Nullable List result) { - futureToSet.set(null); - } - - @Override - public void onFailure(Throwable t) { - log.error("Can't sync widget types by widget bundle [{}]", widgetBundleTypesRequestMsg, t); - futureToSet.setException(t); - } - }, dbCallbackExecutorService); } - return futureToSet; + return Futures.immediateFuture(null); } @Override @@ -416,9 +346,12 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { public void onSuccess(@Nullable List entityViews) { try { if (entityViews != null && !entityViews.isEmpty()) { + List> futures = new ArrayList<>(); for (EntityView entityView : entityViews) { - Futures.addCallback(relationService.checkRelation(tenantId, edge.getId(), entityView.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE), new FutureCallback<>() { + ListenableFuture future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE); + futures.add(future); + Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(@Nullable Boolean result) { if (Boolean.TRUE.equals(result)) { @@ -426,16 +359,27 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { EdgeEventActionType.ADDED, entityView.getId(), null); } } - @Override public void onFailure(Throwable t) { - log.error("Exception during loading relation [{}] to edge on sync!", t, t); - futureToSet.setException(t); + // Do nothing - error handles in allAsList } }, dbCallbackExecutorService); } + Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List result) { + futureToSet.set(null); + } + + @Override + public void onFailure(Throwable t) { + log.error("Exception during loading relation [{}] to edge on sync!", t, t); + futureToSet.setException(t); + } + }, dbCallbackExecutorService); + } else { + futureToSet.set(null); } - futureToSet.set(null); } catch (Exception e) { log.error("Exception during loading relation(s) to edge on sync!", e); futureToSet.setException(e); @@ -451,30 +395,19 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { return futureToSet; } - private ListenableFuture saveEdgeEvent(TenantId tenantId, - EdgeId edgeId, - EdgeEventType type, - EdgeEventActionType action, - EntityId entityId, - JsonNode body) { + private void saveEdgeEvent(TenantId tenantId, + EdgeId edgeId, + EdgeEventType type, + EdgeEventActionType action, + EntityId entityId, + JsonNode body) { log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]", tenantId, edgeId, type, action, entityId, body); EdgeEvent edgeEvent = EdgeEventUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); - ListenableFuture future = edgeEventService.saveAsync(edgeEvent); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable EdgeEvent result) { - tbClusterService.onEdgeEventUpdate(tenantId, edgeId); - } - - @Override - public void onFailure(Throwable t) { - log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t); - } - }, dbCallbackExecutorService); - return future; + edgeEventService.save(edgeEvent); + tbClusterService.onEdgeEventUpdate(tenantId, edgeId); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 51f645a2af..ab1d2bbc61 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -215,6 +215,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { node.put("password", ""); node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself) node.put("enableProxy", false); + node.put("showChangePassword", false); mailSettings.setJsonValue(node); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); } @@ -480,6 +481,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { device.setAdditionalInfo(additionalInfo); } device = deviceService.saveDevice(device); + //TODO: No access to cluster service, so we should manually update the status of device. DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(TenantId.SYS_TENANT_ID, device.getId()); deviceCredentials.setCredentialsId(accessToken); deviceCredentialsService.updateDeviceCredentials(TenantId.SYS_TENANT_ID, deviceCredentials); diff --git a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java index 4e5a104577..c4e780af36 100644 --- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.ota; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; @@ -49,7 +50,7 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import javax.annotation.Nullable; import java.util.ArrayList; @@ -87,10 +88,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { private final RuleEngineTelemetryService telemetryService; private final TbQueueProducer> otaPackageStateMsgProducer; - public DefaultOtaPackageStateService(TbClusterService tbClusterService, OtaPackageService otaPackageService, + public DefaultOtaPackageStateService(@Lazy TbClusterService tbClusterService, + OtaPackageService otaPackageService, DeviceService deviceService, DeviceProfileService deviceProfileService, - RuleEngineTelemetryService telemetryService, + @Lazy RuleEngineTelemetryService telemetryService, TbCoreQueueFactory coreQueueFactory) { this.tbClusterService = tbClusterService; this.otaPackageService = otaPackageService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index fff648401b..0829583fbc 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -16,11 +16,17 @@ package org.thingsboard.server.service.queue; import com.google.protobuf.ByteString; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -29,7 +35,6 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; @@ -56,8 +61,9 @@ import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import java.util.HashSet; import java.util.Set; @@ -66,10 +72,13 @@ import java.util.concurrent.atomic.AtomicInteger; @Service @Slf4j +@RequiredArgsConstructor public class DefaultTbClusterService implements TbClusterService { @Value("${cluster.stats.enabled:false}") private boolean statsEnabled; + @Value("${edges.enabled}") + protected boolean edgesEnabled; private final AtomicInteger toCoreMsgs = new AtomicInteger(0); private final AtomicInteger toCoreNfs = new AtomicInteger(0); @@ -81,13 +90,7 @@ public class DefaultTbClusterService implements TbClusterService { private final PartitionService partitionService; private final DataDecodingEncodingService encodingService; private final TbDeviceProfileCache deviceProfileCache; - - public DefaultTbClusterService(TbQueueProducerProvider producerProvider, PartitionService partitionService, DataDecodingEncodingService encodingService, TbDeviceProfileCache deviceProfileCache) { - this.producerProvider = producerProvider; - this.partitionService = partitionService; - this.encodingService = encodingService; - this.deviceProfileCache = deviceProfileCache; - } + private final OtaPackageStateService otaPackageStateService; @Override public void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback) { @@ -200,55 +203,52 @@ public class DefaultTbClusterService implements TbClusterService { } @Override - public void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { + public void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state) { log.trace("[{}] Processing {} state change event: {}", tenantId, entityId.getEntityType(), state); broadcast(new ComponentLifecycleMsg(tenantId, entityId, state)); } @Override public void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback) { - onEntityChange(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback); + broadcastEntityChangeToTransport(deviceProfile.getTenantId(), deviceProfile.getId(), deviceProfile, callback); } @Override public void onTenantProfileChange(TenantProfile tenantProfile, TbQueueCallback callback) { - onEntityChange(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tenantProfile, callback); + broadcastEntityChangeToTransport(TenantId.SYS_TENANT_ID, tenantProfile.getId(), tenantProfile, callback); } @Override public void onTenantChange(Tenant tenant, TbQueueCallback callback) { - onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); + broadcastEntityChangeToTransport(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); } @Override public void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback) { - onEntityChange(apiUsageState.getTenantId(), apiUsageState.getId(), apiUsageState, callback); + broadcastEntityChangeToTransport(apiUsageState.getTenantId(), apiUsageState.getId(), apiUsageState, callback); broadcast(new ComponentLifecycleMsg(apiUsageState.getTenantId(), apiUsageState.getId(), ComponentLifecycleEvent.UPDATED)); } @Override public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) { - onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); + broadcastEntityDeleteToTransport(entity.getTenantId(), entity.getId(), entity.getName(), callback); } @Override public void onTenantProfileDelete(TenantProfile entity, TbQueueCallback callback) { - onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); + broadcastEntityDeleteToTransport(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); } @Override public void onTenantDelete(Tenant entity, TbQueueCallback callback) { - onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); + broadcastEntityDeleteToTransport(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); } @Override - public void onDeviceChange(Device entity, TbQueueCallback callback) { - onEntityChange(entity.getTenantId(), entity.getId(), entity, callback); - } - - @Override - public void onDeviceDeleted(Device entity, TbQueueCallback callback) { - onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); + public void onDeviceDeleted(Device device, TbQueueCallback callback) { + broadcastEntityDeleteToTransport(device.getTenantId(), device.getId(), device.getName(), callback); + sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), false, false, true); + broadcastEntityStateChangeEvent(device.getTenantId(), device.getId(), ComponentLifecycleEvent.DELETED); } @Override @@ -278,7 +278,7 @@ public class DefaultTbClusterService implements TbClusterService { broadcast(transportMsg, callback); } - public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { + public void broadcastEntityChangeToTransport(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() @@ -288,7 +288,7 @@ public class DefaultTbClusterService implements TbClusterService { broadcast(transportMsg, callback); } - private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { + private void broadcastEntityDeleteToTransport(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { log.trace("[{}][{}][{}] Processing [{}] delete event", tenantId, entityId.getEntityType(), entityId.getId(), name); TransportProtos.EntityDeleteMsg entityDeleteMsg = TransportProtos.EntityDeleteMsg.newBuilder() .setEntityType(entityId.getEntityType().name()) @@ -369,4 +369,72 @@ public class DefaultTbClusterService implements TbClusterService { } } } + + private void sendDeviceStateServiceEvent(TenantId tenantId, DeviceId deviceId, boolean added, boolean updated, boolean deleted) { + TransportProtos.DeviceStateServiceMsgProto.Builder builder = TransportProtos.DeviceStateServiceMsgProto.newBuilder(); + builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); + builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); + builder.setDeviceIdMSB(deviceId.getId().getMostSignificantBits()); + builder.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()); + builder.setAdded(added); + builder.setUpdated(updated); + builder.setDeleted(deleted); + TransportProtos.DeviceStateServiceMsgProto msg = builder.build(); + pushMsgToCore(tenantId, deviceId, TransportProtos.ToCoreMsg.newBuilder().setDeviceStateServiceMsg(msg).build(), null); + } + + @Override + public void onDeviceUpdated(Device device, Device old) { + var created = old == null; + broadcastEntityChangeToTransport(device.getTenantId(), device.getId(), device, null); + if (old != null && (!device.getName().equals(old.getName()) || !device.getType().equals(old.getType()))) { + pushMsgToCore(new DeviceNameOrTypeUpdateMsg(device.getTenantId(), device.getId(), device.getName(), device.getType()), null); + } + broadcastEntityStateChangeEvent(device.getTenantId(), device.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), created, !created, false); + otaPackageStateService.update(device, old); + if (!created) { + sendNotificationMsgToEdgeService(device.getTenantId(), null, device.getId(), null, null, EdgeEventActionType.UPDATED); + } + } + + @Override + public void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { + if (!edgesEnabled) { + return; + } + if (type == null) { + if (entityId != null) { + type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType()); + } else { + log.trace("[{}] entity id and type are null. Ignoring this notification", tenantId); + return; + } + if (type == null) { + log.trace("[{}] edge event type is null. Ignoring this notification [{}]", tenantId, entityId); + return; + } + } + TransportProtos.EdgeNotificationMsgProto.Builder builder = TransportProtos.EdgeNotificationMsgProto.newBuilder(); + builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); + builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); + builder.setType(type.name()); + builder.setAction(action.name()); + if (entityId != null) { + builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); + builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits()); + builder.setEntityType(entityId.getEntityType().name()); + } + if (edgeId != null) { + builder.setEdgeIdMSB(edgeId.getId().getMostSignificantBits()); + builder.setEdgeIdLSB(edgeId.getId().getLeastSignificantBits()); + } + if (body != null) { + builder.setBody(body); + } + TransportProtos.EdgeNotificationMsgProto msg = builder.build(); + log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg); + pushMsgToCore(tenantId, entityId != null ? entityId : tenantId, TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index ceb364f921..ad7a0340cf 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -26,7 +26,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.TenantId; @@ -64,7 +64,7 @@ import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.IdMsgPair; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.state.DeviceStateService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 2edaceee8e..4b1392e14a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -21,7 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; @@ -55,7 +55,7 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStr import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.stats.RuleEngineStatisticsService; diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index 1ef8d9361e..46e49fcdc8 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -22,18 +22,19 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import javax.annotation.PostConstruct; @@ -138,6 +139,12 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { } } + @Override + public void processRemoveRpc(RemoveRpcActorMsg removeRpcMsg) { + log.trace("[{}][{}] Processing remove RPC [{}]", removeRpcMsg.getTenantId(), removeRpcMsg.getRequestId(), removeRpcMsg.getDeviceId()); + actorContext.tellWithHighPriority(removeRpcMsg); + } + private void sendRpcResponseToTbRuleEngine(String originServiceId, FromDeviceRpcResponse response) { if (serviceId.equals(originServiceId)) { if (tbRuleEngineRpcService.isPresent()) { @@ -168,6 +175,8 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { entityNode.put("method", msg.getBody().getMethod()); entityNode.put("params", msg.getBody().getParams()); + entityNode.put(DataConstants.ADDITIONAL_INFO, msg.getAdditionalInfo()); + try { TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), currentUser.getCustomerId(), metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java index e16af3695b..230f6e759e 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java @@ -19,18 +19,19 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbRuleEngineComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -100,7 +101,7 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi @Override public void sendRpcRequestToDevice(RuleEngineDeviceRpcRequest src, Consumer consumer) { ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), src.getTenantId(), src.getDeviceId(), - src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()), src.isPersisted()); + src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()), src.isPersisted(), src.getAdditionalInfo()); forwardRpcRequestToDeviceActor(request, response -> { if (src.isRestApiCall()) { sendRpcResponseToTbCore(src.getOriginServiceId(), response); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java index 9efe9a3b1f..1316c08a45 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponseActorMsg.java @@ -18,10 +18,11 @@ package org.thingsboard.server.service.rpc; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; -import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; @ToString @RequiredArgsConstructor diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java new file mode 100644 index 0000000000..d25090247a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/rpc/RemoveRpcActorMsg.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.rpc; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; + +import java.util.UUID; + +@ToString +@RequiredArgsConstructor +public class RemoveRpcActorMsg implements ToDeviceActorNotificationMsg { + + @Getter + private final TenantId tenantId; + @Getter + private final DeviceId deviceId; + + @Getter + private final UUID requestId; + + @Override + public MsgType getMsgType() { + return MsgType.REMOVE_RPC_TO_DEVICE_ACTOR_MSG; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java index c3c6f9b888..c68b4a7ac7 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.rpc; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.service.security.model.SecurityUser; @@ -55,4 +56,6 @@ public interface TbCoreDeviceRpcService { */ void processRpcResponseFromDeviceActor(FromDeviceRpcResponse response); + void processRemoveRpc(RemoveRpcActorMsg removeRpcMsg); + } diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java index 456ea7d6d6..5a7f3de1f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; @TbCoreComponent @Service diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java index 3b920ac2eb..34197797e6 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRuleEngineDeviceRpcService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.rpc; import org.thingsboard.rule.engine.api.RuleEngineRpcService; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; /** * Created by ashvayka on 16.04.18. diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java index 64a08b2ef4..7575e8b434 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java @@ -18,7 +18,7 @@ package org.thingsboard.server.service.rpc; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; -import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index f06dfc0711..4d4780f7ca 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -47,7 +47,7 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.install.InstallScripts; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; @@ -180,7 +180,7 @@ public abstract class AbstractOAuth2ClientMapper { installScripts.createDefaultEdgeRuleChains(tenant.getId()); tenantProfileCache.evict(tenant.getId()); tbClusterService.onTenantChange(tenant, null); - tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), ComponentLifecycleEvent.CREATED); } else { tenant = tenants.get(0); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 778f7416ff..93b3fb11b8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.security.auth.oauth2; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; +import javax.annotation.PostConstruct; import javax.servlet.http.HttpServletRequest; @Service(value = "customOAuth2ClientMapper") @@ -40,6 +42,15 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); + @PostConstruct + public void init() { + // Register time module to parse Instant objects. + // com.fasterxml.jackson.databind.exc.InvalidDefinitionException: + // Java 8 date/time type `java.time.Instant` not supported by default: + // add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling + json.registerModule(new JavaTimeModule()); + } + @Override public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { OAuth2MapperConfig config = registration.getMapperConfig(); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 303a430e77..3c7eb12ef3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.security.auth.oauth2; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; @@ -42,6 +43,7 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.UUID; +@Slf4j @Component(value = "oauth2AuthenticationSuccessHandler") public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { @@ -99,6 +101,8 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS clearAuthenticationAttributes(request, response); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); } catch (Exception e) { + log.debug("Error occurred during processing authentication success result. " + + "request [{}], response [{}], authentication [{}]", request, response, authentication, e); clearAuthenticationAttributes(request, response); String errorPrefix; if (!StringUtils.isEmpty(callbackUrlScheme)) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index bf9fb066a0..bc322fe04d 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -22,7 +22,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import com.google.common.util.concurrent.SettableFuture; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -59,7 +58,7 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.Nonnull; @@ -71,7 +70,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Queue; import java.util.Random; import java.util.Set; @@ -137,7 +135,6 @@ public class DefaultDeviceStateService extends TbApplicationEventListener> partitionedDevices = new ConcurrentHashMap<>(); final ConcurrentMap deviceStates = new ConcurrentHashMap<>(); - private final ConcurrentMap deviceLastSavedActivity = new ConcurrentHashMap<>(); final Queue> subscribeQueue = new ConcurrentLinkedQueue<>(); @@ -177,22 +174,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener 0 && lastReportedActivity > lastSavedActivity) { - final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); + final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); + if (lastReportedActivity > 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) { updateActivityState(deviceId, stateData, lastReportedActivity); } + cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId); } void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) { log.trace("updateActivityState - fetched state {} for device {}, lastReportedActivity {}", stateData, deviceId, lastReportedActivity); if (stateData != null) { save(deviceId, LAST_ACTIVITY_TIME, lastReportedActivity); - deviceLastSavedActivity.put(deviceId, lastReportedActivity); DeviceState state = stateData.getState(); state.setLastActivityTime(lastReportedActivity); if (!state.isActive()) { @@ -225,21 +207,23 @@ public class DefaultDeviceStateService extends TbApplicationEventListener { Set devices = partitionedDevices.remove(partition); - devices.forEach(deviceId -> { - deviceStates.remove(deviceId); - deviceLastSavedActivity.remove(deviceId); - }); + devices.forEach(this::cleanUpDeviceStateMap); }); addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet())); @@ -457,17 +439,18 @@ public class DefaultDeviceStateService extends TbApplicationEventListener deviceIds = new HashSet<>(deviceStates.keySet()); - for (DeviceId deviceId : deviceIds) { - updateInactivityStateIfExpired(ts, deviceId); - } + partitionedDevices.forEach((tpi, deviceIds) -> { + log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size()); + for (DeviceId deviceId : deviceIds) { + updateInactivityStateIfExpired(ts, deviceId); + } + }); } void updateInactivityStateIfExpired(long ts, DeviceId deviceId) { @@ -488,8 +471,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener deviceIdSet = partitionedDevices.get(tpi); deviceIdSet.remove(deviceId); } + private void cleanUpDeviceStateMap(DeviceId deviceId) { + deviceStates.remove(deviceId); + } + private ListenableFuture fetchDeviceState(Device device) { ListenableFuture future; if (persistToTelemetry) { @@ -573,7 +554,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener Function, DeviceStateData> extractDeviceStateData(Device device) { - return new Function, DeviceStateData>() { + return new Function<>() { @Nonnull @Override public DeviceStateData apply(@Nullable List data) { @@ -660,9 +641,9 @@ public class DefaultDeviceStateService extends TbApplicationEventListener(deviceId, key, value)); + new TelemetrySaveCallback<>(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } @@ -671,18 +652,18 @@ public class DefaultDeviceStateService extends TbApplicationEventListener(deviceId, key, value)); + new TelemetrySaveCallback<>(deviceId, key, value)); } else { - tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback<>(deviceId, key, value)); + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, deviceId, DataConstants.SERVER_SCOPE, key, value, new TelemetrySaveCallback<>(deviceId, key, value)); } } - private static class AttributeSaveCallback implements FutureCallback { + private static class TelemetrySaveCallback implements FutureCallback { private final DeviceId deviceId; private final String key; private final Object value; - AttributeSaveCallback(DeviceId deviceId, String key, Object value) { + TelemetrySaveCallback(DeviceId deviceId, String key, Object value) { this.deviceId = deviceId; this.key = key; this.value = value; diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java index b1bbb9cdfa..025a29dddb 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.state; import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.common.msg.queue.TbCallback; @@ -27,19 +28,13 @@ import org.thingsboard.server.common.msg.queue.TbCallback; */ public interface DeviceStateService extends ApplicationListener { - void onDeviceAdded(Device device); + void onDeviceConnect(TenantId tenantId, DeviceId deviceId); - void onDeviceUpdated(Device device); + void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivityTime); - void onDeviceDeleted(Device device); + void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId); - void onDeviceConnect(DeviceId deviceId); - - void onDeviceActivity(DeviceId deviceId, long lastReportedActivityTime); - - void onDeviceDisconnect(DeviceId deviceId); - - void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout); + void onDeviceInactivityTimeoutUpdate(TenantId tenantId, DeviceId deviceId, long inactivityTimeout); void onQueueMsg(TransportProtos.DeviceStateServiceMsgProto proto, TbCallback bytes); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java index 5a9051265d..15cf6f1a17 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java @@ -53,7 +53,7 @@ import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.state.DefaultDeviceStateService; import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; @@ -224,7 +224,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return subscriptionUpdate; }); if (entityId.getEntityType() == EntityType.DEVICE) { - updateDeviceInactivityTimeout(entityId, ts); + updateDeviceInactivityTimeout(tenantId, entityId, ts); } callback.onSuccess(); } @@ -259,7 +259,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene }); if (entityId.getEntityType() == EntityType.DEVICE) { if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope)) { - updateDeviceInactivityTimeout(entityId, attributes); + updateDeviceInactivityTimeout(tenantId, entityId, attributes); } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, new DeviceId(entityId.getId()), DataConstants.SHARED_SCOPE, new ArrayList<>(attributes)) @@ -269,10 +269,10 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene callback.onSuccess(); } - private void updateDeviceInactivityTimeout(EntityId entityId, List kvEntries) { + private void updateDeviceInactivityTimeout(TenantId tenantId, EntityId entityId, List kvEntries) { for (KvEntry kvEntry : kvEntries) { if (kvEntry.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) { - deviceStateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), kvEntry.getLongValue().orElse(0L)); + deviceStateService.onDeviceInactivityTimeoutUpdate(tenantId, new DeviceId(entityId.getId()), kvEntry.getLongValue().orElse(0L)); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 0f1765f316..7db0478e03 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; @@ -42,7 +42,6 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; @Slf4j @TbCoreComponent diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index 23abba15f3..bbd3811075 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -20,15 +20,13 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationListener; -import org.springframework.context.event.EventListener; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import javax.annotation.Nullable; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index d85ac8d736..14d264fb17 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -46,7 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 33173283b3..a3771988cf 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -45,7 +45,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import javax.annotation.Nullable; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index bb34b14e19..55a04b108b 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -95,7 +95,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.state.DeviceStateService; @@ -265,9 +265,11 @@ public class DefaultTransportApiService implements TransportApiService { device.setCustomerId(gateway.getCustomerId()); DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType()); device.setDeviceProfileId(deviceProfile.getId()); - device = deviceService.saveDevice(device); + Device savedDevice = deviceService.saveDevice(device); + tbClusterService.onDeviceUpdated(savedDevice, device); + device = savedDevice; + relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); - deviceStateService.onDeviceAdded(device); TbMsgMetaData metaData = new TbMsgMetaData(); CustomerId customerId = gateway.getCustomerId(); @@ -581,7 +583,7 @@ public class DefaultTransportApiService implements TransportApiService { device.setName(deviceName); device.setType("LwM2M"); device = deviceService.saveDevice(device); - deviceStateService.onDeviceAdded(device); + tbClusterService.onDeviceUpdated(device, null); } TransportProtos.LwM2MRegistrationResponseMsg registrationResponseMsg = TransportProtos.LwM2MRegistrationResponseMsg.newBuilder() diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 0cede00913..265d4d1bbc 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -588,6 +588,7 @@ transport: bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" bind_port: "${MQTT_BIND_PORT:1883}" timeout: "${MQTT_TIMEOUT:10000}" + msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism netty: leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" @@ -747,6 +748,8 @@ queue: compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}" @@ -763,7 +766,11 @@ queue: tb_ota_package: - key: max.poll.records value: 10 - other: + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java index c07a499f83..b809b9cbc5 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; +import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -28,13 +29,14 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.Authority; import java.util.List; +import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -83,34 +85,37 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest { Device device = constructDevice("TestDevice", "default"); Device savedDevice = doPost("/api/device", device, Device.class); - doPost("/api/edge/" + edge.getId().toString() + "/device/" + savedDevice.getId().toString(), Device.class); + final EdgeId edgeId = edge.getId(); + doPost("/api/edge/" + edgeId.toString() + "/device/" + savedDevice.getId().toString(), Device.class); Asset asset = constructAsset("TestAsset", "default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); - doPost("/api/edge/" + edge.getId().toString() + "/asset/" + savedAsset.getId().toString(), Asset.class); + doPost("/api/edge/" + edgeId.toString() + "/asset/" + savedAsset.getId().toString(), Asset.class); EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE); doPost("/api/relation", relation); - // wait while edge event for the relation entity persisted to DB - Thread.sleep(100); - List edgeEvents; - int attempt = 1; - do { - edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?", - new TypeReference>() {}, new TimePageLink(4)).getData(); - attempt++; - Thread.sleep(100); - } while (edgeEvents.size() != 4 && attempt < 5); - Assert.assertEquals(4, edgeEvents.size()); + Awaitility.await() + .atMost(30, TimeUnit.SECONDS) + .until(() -> { + List edgeEvents = findEdgeEvents(edgeId); + return edgeEvents.size() == 4; + }); + List edgeEvents = findEdgeEvents(edgeId); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.ASSET.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType()))); } + private List findEdgeEvents(EdgeId edgeId) throws Exception { + return doGetTypedWithTimePageLink("/api/edge/" + edgeId.toString() + "/events?", + new TypeReference>() { + }, new TimePageLink(10)).getData(); + } + private Device constructDevice(String name, String type) { Device device = new Device(); device.setName(name); diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index becdfabb32..81042a47fb 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -27,6 +27,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageLite; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; +import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -114,7 +115,7 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import java.util.ArrayList; import java.util.List; @@ -126,6 +127,7 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.service.edge.rpc.EdgeProtoUtils.getStringValue; @Slf4j abstract public class BaseEdgeTest extends AbstractControllerTest { @@ -648,7 +650,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits()); Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); - Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); + Assert.assertEquals(relationUpdateMsg.getTypeGroup().getValue(), relation.getTypeGroup().name()); // 2 edgeImitator.expectMessageAmount(1); @@ -672,7 +674,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits()); Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); - Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); + Assert.assertEquals(relationUpdateMsg.getTypeGroup().getValue(), relation.getTypeGroup().name()); log.info("Relations tested successfully"); } @@ -730,7 +732,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectMessageAmount(1); doDelete("/api/alarm/" + savedAlarm.getId().getId().toString()) .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); + Assert.assertTrue(edgeImitator.waitForMessages(1)); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); + alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); + Assert.assertEquals(alarmUpdateMsg.getType(), savedAlarm.getType()); + Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName()); + Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); + Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name()); log.info("Alarms tested successfully"); } @@ -900,9 +910,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType()); Assert.assertEquals(widgetTypeUpdateMsg.getIdMSB(), savedWidgetType.getUuidId().getMostSignificantBits()); Assert.assertEquals(widgetTypeUpdateMsg.getIdLSB(), savedWidgetType.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(widgetTypeUpdateMsg.getAlias(), savedWidgetType.getAlias()); - Assert.assertEquals(widgetTypeUpdateMsg.getName(), savedWidgetType.getName()); - Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor()); + Assert.assertEquals(widgetTypeUpdateMsg.getAlias().getValue(), savedWidgetType.getAlias()); + Assert.assertEquals(widgetTypeUpdateMsg.getName().getValue(), savedWidgetType.getName()); + Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson().getValue()), savedWidgetType.getDescriptor()); // 3 edgeImitator.expectMessageAmount(1); @@ -939,7 +949,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); - edgeEventService.saveAsync(edgeEvent); + edgeEventService.save(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -978,7 +988,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { JsonNode attributesEntityData = mapper.readTree(attributesData); EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData); edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent1); + edgeEventService.save(edgeEvent1); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -1003,7 +1013,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { JsonNode postAttributesEntityData = mapper.readTree(postAttributesData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData); edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent); + edgeEventService.save(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -1028,7 +1038,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData); edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent); + edgeEventService.save(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -1062,7 +1072,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.RPC_CALL, device.getId().getId(), EdgeEventType.DEVICE, body); edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent); + edgeEventService.save(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -1088,7 +1098,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); - edgeEventService.saveAsync(edgeEvent); + edgeEventService.save(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); } @@ -1202,7 +1212,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage; Assert.assertNotEquals(deviceOnCloudName, latestDeviceUpdateMsg.getName()); - Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName()); + Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName().getValue()); UUID newDeviceId = new UUID(latestDeviceUpdateMsg.getIdMSB(), latestDeviceUpdateMsg.getIdLSB()); @@ -1269,7 +1279,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { EntityId toEntityId = EntityIdFactory.getByTypeAndUuid(relationUpdateMsg.getToEntityType(), toUUID); Assert.assertEquals(relation.getTo(), toEntityId); - Assert.assertEquals(relation.getTypeGroup().name(), relationUpdateMsg.getTypeGroup()); + Assert.assertEquals(relation.getTypeGroup().name(), relationUpdateMsg.getTypeGroup().getValue()); } private void sendAlarm() throws Exception { @@ -1348,15 +1358,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build()); Assert.assertTrue(edgeImitator.waitForResponses()); - int attempt = 0; - Map>> timeseries; - do { - timeseries = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey, - new TypeReference<>() {}); - // Wait before device attributes saved to database before requesting them from controller - Thread.sleep(100); - attempt++; - } while (!timeseries.containsKey(timeseriesKey) || attempt < 10); + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .until(() -> loadDeviceTimeseries(device, timeseriesKey).containsKey(timeseriesKey)); + + Map>> timeseries = loadDeviceTimeseries(device, timeseriesKey); Assert.assertTrue(timeseries.containsKey(timeseriesKey)); Assert.assertEquals(1, timeseries.get(timeseriesKey).size()); Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value")); @@ -1365,7 +1371,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(1, attributes.size()); Assert.assertEquals(attributes.get(0).get("key"), attributesKey); Assert.assertEquals(attributes.get(0).get("value"), attributesValue); + } + private Map>> loadDeviceTimeseries(Device device, String timeseriesKey) throws Exception { + return doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey, + new TypeReference<>() {}); } private void sendRelation() throws Exception { @@ -1381,7 +1391,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); RelationUpdateMsg.Builder relationUpdateMsgBuilder = RelationUpdateMsg.newBuilder(); relationUpdateMsgBuilder.setType("test"); - relationUpdateMsgBuilder.setTypeGroup(RelationTypeGroup.COMMON.name()); + relationUpdateMsgBuilder.setTypeGroup(getStringValue(RelationTypeGroup.COMMON.name())); relationUpdateMsgBuilder.setToIdMSB(device1.getId().getId().getMostSignificantBits()); relationUpdateMsgBuilder.setToIdLSB(device1.getId().getId().getLeastSignificantBits()); relationUpdateMsgBuilder.setToEntityType(device1.getId().getEntityType().name()); @@ -1447,7 +1457,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages());; + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index 98118409bc..ed32020fb7 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -27,7 +27,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.cluster.TbClusterService; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java index 3c47b2497e..2a5e46473a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java @@ -43,7 +43,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}"; String deviceId = savedDevice.getId().getId().toString(); - doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409), + doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(504), asyncContextTimeoutToUseRpcPlugin); } @@ -52,7 +52,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}"; String nonExistentDeviceId = Uuids.timeBased().toString(); - String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, + String result = doPostAsync("/api/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, status().isNotFound()); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); } @@ -62,7 +62,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}"; String deviceId = savedDevice.getId().getId().toString(); - doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409), + doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(504), asyncContextTimeoutToUseRpcPlugin); } @@ -71,7 +71,7 @@ public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}"; String nonExistentDeviceId = Uuids.timeBased().toString(); - String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, + String result = doPostAsync("/api/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, status().isNotFound()); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index e2567576f3..758dc02004 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -71,7 +71,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); latch.await(3, TimeUnit.SECONDS); @@ -99,14 +99,14 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - String actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); latch.await(3, TimeUnit.SECONDS); validateTwoWayStateChangedNotification(callback, 1, expectedResponseResult, actualResult); latch = new CountDownLatch(1); - actualResult = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); latch.await(3, TimeUnit.SECONDS); validateTwoWayStateChangedNotification(callback, 2, expectedResponseResult, actualResult); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 1cab815e6a..a7cd4ddf79 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -27,6 +27,7 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; @@ -261,7 +262,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { @Before public void beforeTest() throws Exception { - executor = Executors.newScheduledThreadPool(10); + executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled")); loginTenantAdmin(); String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 6703d79713..d97708fe5b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -16,6 +16,8 @@ package org.thingsboard.server.transport.lwm2m; import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.Device; @@ -23,16 +25,18 @@ import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCr import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; -import org.thingsboard.server.common.data.query.EntityKey; -import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.UUID; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.Matchers.is; import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; @@ -43,8 +47,10 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDA import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; +@Slf4j public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + public static final int TIMEOUT = 30; private final String OTA_TRANSPORT_CONFIGURATION = "{\n" + " \"observeAttr\": {\n" + " \"keyName\": {\n" + @@ -122,6 +128,15 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { " \"type\": \"LWM2M\"\n" + "}"; + LwM2MTestClient client = null; + + @After + public void tearDown() { + if (client != null) { + client.destroy(); + } + } + @Test public void testConnectAndObserveTelemetry() throws Exception { NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); @@ -196,37 +211,68 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { } } + /** + * This is the example how to use the AWAITILITY instead Thread.sleep() + * Test will finish as fast as possible, but will await until TIMEOUT if a build machine is busy or slow + * Check the detailed log output to learn how Awaitility polling the API and when exactly expected result appears + * */ @Test public void testSoftwareUpdateByObject9() throws Exception { - LwM2MTestClient client = null; - try { - createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint("OTA_" + ENDPOINT); - Device device = createDevice(clientCredentials); - - device.setSoftwareId(createSoftware().getId()); - device = doPost("/api/device", device, Device.class); + //given + final List expectedStatuses = Collections.unmodifiableList(Arrays.asList( + QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED)); - Thread.sleep(1000); - - client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); - client.init(SECURITY, COAP_CONFIG); - - Thread.sleep(3000); - - List ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?orderBy=ASC&keys=sw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { - })); - - List statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); + clientCredentials.setEndpoint("OTA_" + ENDPOINT); + final Device device = createDevice(clientCredentials); + device.setSoftwareId(createSoftware().getId()); + + log.warn("Saving by API " + device); + final Device savedDevice = doPost("/api/device", device, Device.class); + Assert.assertNotNull(savedDevice); + log.warn("Device saved by API {}", savedDevice); + + log.warn("AWAIT atMost {} SECONDS on get device by API...", TIMEOUT); + await() + .atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> getDeviceFromAPI(device.getId().getId()), is(savedDevice)); + log.warn("Got device by API."); + + //when + log.warn("Init the client..."); + client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); + client.init(SECURITY, COAP_CONFIG); + log.warn("Init done"); + + log.warn("AWAIT atMost {} SECONDS on timeseries List by API with list size {}...", TIMEOUT, expectedStatuses.size()); + await() + .atMost(30, TimeUnit.SECONDS) + .until(() -> getSwStateTelemetryFromAPI(device.getId().getId()) + .size(), is(expectedStatuses.size())); + log.warn("Got an expected await condition!"); + + //then + log.warn("Fetching ts for the final asserts"); + List ts = getSwStateTelemetryFromAPI(device.getId().getId()); + log.warn("Got an ts {}", ts); + + List statuses = ts.stream().sorted(Comparator.comparingLong(TsKvEntry::getTs)).map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + log.warn("Converted ts to statuses {}", statuses); + + Assert.assertEquals(expectedStatuses, statuses); + } - List expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); + private Device getDeviceFromAPI(UUID deviceId) throws Exception { + final Device device = doGet("/api/device/" + deviceId, Device.class); + log.warn("Fetched device by API for deviceId {}, device is {}", deviceId, device); + return device; + } - Assert.assertEquals(expectedStatuses, statuses); - } finally { - if (client != null) { - client.destroy(); - } - } + private List getSwStateTelemetryFromAPI(UUID deviceId) throws Exception { + final List tsKvEntries = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?orderBy=ASC&keys=sw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { + })); + log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries); + return tsKvEntries; } } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java index 32f770a41c..b5f005cd00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java @@ -46,7 +46,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"24\",\"value\": 1},\"timeout\": 6000}"; String deviceId = savedDevice.getId().getId().toString(); - doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(409), + doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(504), asyncContextTimeoutToUseRpcPlugin); } @@ -55,7 +55,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"25\",\"value\": 1}}"; String nonExistentDeviceId = Uuids.timeBased().toString(); - String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, + String result = doPostAsync("/api/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, status().isNotFound()); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); } @@ -65,7 +65,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"27\",\"value\": 1},\"timeout\": 6000}"; String deviceId = savedDevice.getId().getId().toString(); - doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(409), + doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(504), asyncContextTimeoutToUseRpcPlugin); } @@ -74,7 +74,7 @@ public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends Ab String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"28\",\"value\": 1}}"; String nonExistentDeviceId = Uuids.timeBased().toString(); - String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, + String result = doPostAsync("/api/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, status().isNotFound()); Assert.assertEquals(AccessValidator.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index f24cb0c2bc..9f83f24bcb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -69,7 +69,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); Assert.assertTrue(StringUtils.isEmpty(result)); latch.await(3, TimeUnit.SECONDS); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); @@ -95,7 +95,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String expected = "{\"value1\":\"A\",\"value2\":\"B\"}"; latch.await(3, TimeUnit.SECONDS); Assert.assertEquals(expected, result); @@ -130,7 +130,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String deviceId = savedDevice.getId().getId().toString(); - String result = doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); Assert.assertTrue(StringUtils.isEmpty(result)); latch.await(3, TimeUnit.SECONDS); assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS()); @@ -156,7 +156,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM String setGpioRequest = "{\"method\": \"toggle_gpio\", \"params\": {\"pin\":1}}"; String deviceId = savedDevice.getId().getId().toString(); - String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); latch.await(3, TimeUnit.SECONDS); String expected = "{\"success\":true}"; assertEquals(expected, result); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index cf9ae499c4..3792d66ac4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -131,7 +131,7 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String result = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}"; latch.await(3, TimeUnit.SECONDS); Assert.assertEquals(expected, result); diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml new file mode 100644 index 0000000000..dfe2657e4c --- /dev/null +++ b/common/cluster-api/pom.xml @@ -0,0 +1,144 @@ + + + 4.0.0 + + org.thingsboard + 3.3.0-SNAPSHOT + common + + org.thingsboard.common + cluster-api + jar + + Thingsboard Server Common Cluster API + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard.common + data + + + org.thingsboard.common + message + + + org.thingsboard.common + stats + + + com.google.guava + guava + + + javax.annotation + javax.annotation-api + + + com.github.fge + json-schema-validator + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + com.fasterxml.jackson.core + jackson-databind + + + org.springframework.boot + spring-boot-autoconfigure + provided + + + com.datastax.oss + java-driver-core + provided + + + io.dropwizard.metrics + metrics-jmx + provided + + + org.apache.commons + commons-lang3 + provided + + + junit + junit + test + + + org.mockito + mockito-core + test + + + + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + false + + + + + + + diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java similarity index 83% rename from application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java rename to common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java index c5848bed58..fce6945f98 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java @@ -13,26 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.queue; +package org.thingsboard.server.cluster; -import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.queue.TbQueueCallback; -import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import java.util.UUID; @@ -54,7 +57,7 @@ public interface TbClusterService { void pushNotificationToTransport(String targetServiceId, ToTransportMsg response, TbQueueCallback callback); - void onEntityStateChange(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); + void broadcastEntityStateChangeEvent(TenantId tenantId, EntityId entityId, ComponentLifecycleEvent state); void onDeviceProfileChange(DeviceProfile deviceProfile, TbQueueCallback callback); @@ -70,7 +73,7 @@ public interface TbClusterService { void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback); - void onDeviceChange(Device device, TbQueueCallback callback); + void onDeviceUpdated(Device device, Device old); void onDeviceDeleted(Device device, TbQueueCallback callback); @@ -79,4 +82,6 @@ public interface TbClusterService { void onResourceDeleted(TbResource resource, TbQueueCallback callback); void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId); + + void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueAdmin.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueCallback.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueCallback.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueCallback.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueCallback.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueConsumer.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueConsumer.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueConsumer.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueConsumer.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueHandler.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueHandler.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueHandler.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueHandler.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsg.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsg.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsg.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsg.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsgDecoder.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsgDecoder.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsgDecoder.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsgDecoder.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsgHeaders.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsgHeaders.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsgHeaders.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsgHeaders.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsgMetadata.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsgMetadata.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueMsgMetadata.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueMsgMetadata.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueProducer.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueProducer.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueProducer.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueProducer.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java b/common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java similarity index 100% rename from common/queue/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java rename to common/cluster-api/src/main/java/org/thingsboard/server/queue/TbQueueResponseTemplate.java diff --git a/common/queue/src/main/proto/jsinvoke.proto b/common/cluster-api/src/main/proto/jsinvoke.proto similarity index 100% rename from common/queue/src/main/proto/jsinvoke.proto rename to common/cluster-api/src/main/proto/jsinvoke.proto diff --git a/common/queue/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto similarity index 100% rename from common/queue/src/main/proto/queue.proto rename to common/cluster-api/src/main/proto/queue.proto diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index feb23d1e25..a78bc44854 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -54,6 +54,8 @@ public interface DeviceService { Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials); + Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile); + void createAccessTokenCredentials(Device device, String accessToken); Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, CustomerId customerId); @@ -100,8 +102,6 @@ public interface DeviceService { Device assignDeviceToTenant(TenantId tenantId, Device device); - Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile); - PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink); Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java index d70ddd1184..f98b461694 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeEventService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.edge; -import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; @@ -24,7 +23,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; public interface EdgeEventService { - ListenableFuture saveAsync(EdgeEvent edgeEvent); + EdgeEvent save(EdgeEvent edgeEvent); PageData findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 329e74d702..ae51749056 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -36,6 +36,7 @@ public class DataConstants { public static final String ALARM_CONDITION_REPEATS = "alarmConditionRepeats"; public static final String ALARM_CONDITION_DURATION = "alarmConditionDuration"; public static final String PERSISTENT = "persistent"; + public static final String ADDITIONAL_INFO = "additionalInfo"; public static final String COAP_TRANSPORT_NAME = "COAP"; public static final String LWM2M_TRANSPORT_NAME = "LWM2M"; public static final String MQTT_TRANSPORT_NAME = "MQTT"; @@ -88,6 +89,7 @@ public class DataConstants { public static final String RPC_SUCCESSFUL = "RPC_SUCCESSFUL"; public static final String RPC_TIMEOUT = "RPC_TIMEOUT"; public static final String RPC_FAILED = "RPC_FAILED"; + public static final String RPC_DELETED = "RPC_DELETED"; public static final String DEFAULT_SECRET_KEY = ""; public static final String SECRET_KEY_FIELD_NAME = "secretKey"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java index 504c89b569..27326087f7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java @@ -33,6 +33,7 @@ public class Rpc extends BaseData implements HasTenantId { private JsonNode request; private JsonNode response; private RpcStatus status; + private JsonNode additionalInfo; public Rpc() { super(); @@ -50,5 +51,6 @@ public class Rpc extends BaseData implements HasTenantId { this.request = rpc.getRequest(); this.response = rpc.getResponse(); this.status = rpc.getStatus(); + this.additionalInfo = rpc.getAdditionalInfo(); } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RpcError.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcError.java similarity index 93% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RpcError.java rename to common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcError.java index 05c27ab654..072a3e63a0 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RpcError.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcError.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api; +package org.thingsboard.server.common.data.rpc; /** * @author Andrew Shvayka diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 9e0a180dcf..06eec3c2ce 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -20,6 +20,7 @@ option java_multiple_files = true; option java_outer_classname = "EdgeProtos"; import "queue.proto"; +import "google/protobuf/wrappers.proto"; package edge; @@ -129,8 +130,8 @@ message RuleChainUpdateMsg { int64 idMSB = 2; int64 idLSB = 3; string name = 4; - int64 firstRuleNodeIdMSB = 5; - int64 firstRuleNodeIdLSB = 6; + google.protobuf.Int64Value firstRuleNodeIdMSB = 5; + google.protobuf.Int64Value firstRuleNodeIdLSB = 6; bool root = 7; bool debugMode = 8; string configuration = 9; @@ -174,8 +175,8 @@ message DashboardUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - int64 customerIdMSB = 4; - int64 customerIdLSB = 5; + google.protobuf.Int64Value customerIdMSB = 4; + google.protobuf.Int64Value customerIdLSB = 5; string title = 6; string configuration = 7; } @@ -184,15 +185,15 @@ message DeviceUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - int64 customerIdMSB = 4; - int64 customerIdLSB = 5; - int64 deviceProfileIdMSB = 6; - int64 deviceProfileIdLSB = 7; + google.protobuf.Int64Value customerIdMSB = 4; + google.protobuf.Int64Value customerIdLSB = 5; + google.protobuf.Int64Value deviceProfileIdMSB = 6; + google.protobuf.Int64Value deviceProfileIdLSB = 7; string name = 8; string type = 9; - string label = 10; - string additionalInfo = 11; - string conflictName = 12; + google.protobuf.StringValue label = 10; + google.protobuf.StringValue additionalInfo = 11; + google.protobuf.StringValue conflictName = 12; } message DeviceProfileUpdateMsg { @@ -200,17 +201,17 @@ message DeviceProfileUpdateMsg { int64 idMSB = 2; int64 idLSB = 3; string name = 4; - string description = 5; + google.protobuf.StringValue description = 5; bool default = 6; string type = 7; - string transportType = 8; - string provisionType = 9; + google.protobuf.StringValue transportType = 8; + google.protobuf.StringValue provisionType = 9; int64 defaultRuleChainIdMSB = 10; int64 defaultRuleChainIdLSB = 11; string defaultQueueName = 12; bytes profileDataBytes = 13; - string provisionDeviceKey = 14; - bytes image = 15; + google.protobuf.StringValue provisionDeviceKey = 14; + google.protobuf.BytesValue image = 15; } message DeviceCredentialsUpdateMsg { @@ -218,49 +219,51 @@ message DeviceCredentialsUpdateMsg { int64 deviceIdLSB = 2; string credentialsType = 3; string credentialsId = 4; - string credentialsValue = 5; + google.protobuf.StringValue credentialsValue = 5; } message AssetUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - int64 customerIdMSB = 4; - int64 customerIdLSB = 5; + google.protobuf.Int64Value customerIdMSB = 4; + google.protobuf.Int64Value customerIdLSB = 5; string name = 6; string type = 7; - string label = 8; - string additionalInfo = 9; + google.protobuf.StringValue label = 8; + google.protobuf.StringValue additionalInfo = 9; } message EntityViewUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - int64 customerIdMSB = 4; - int64 customerIdLSB = 5; + google.protobuf.Int64Value customerIdMSB = 4; + google.protobuf.Int64Value customerIdLSB = 5; string name = 6; string type = 7; int64 entityIdMSB = 8; int64 entityIdLSB = 9; EdgeEntityType entityType = 10; - string additionalInfo = 11; + google.protobuf.StringValue additionalInfo = 11; } message AlarmUpdateMsg { UpdateMsgType msgType = 1; - string name = 2; - string type = 3; - string originatorType = 4; - string originatorName = 5; - string severity = 6; - string status = 7; - int64 startTs = 8; - int64 endTs = 9; - int64 ackTs = 10; - int64 clearTs = 11; - string details = 12; - bool propagate = 13; + int64 idMSB = 2; + int64 idLSB = 3; + string name = 4; + string type = 5; + string originatorType = 6; + string originatorName = 7; + string severity = 8; + string status = 9; + int64 startTs = 10; + int64 endTs = 11; + int64 ackTs = 12; + int64 clearTs = 13; + string details = 14; + bool propagate = 15; } message CustomerUpdateMsg { @@ -268,15 +271,15 @@ message CustomerUpdateMsg { int64 idMSB = 2; int64 idLSB = 3; string title = 4; - string country = 5; - string state = 6; - string city = 7; - string address = 8; - string address2 = 9; - string zip = 10; - string phone = 11; - string email = 12; - string additionalInfo = 13; + google.protobuf.StringValue country = 5; + google.protobuf.StringValue state = 6; + google.protobuf.StringValue city = 7; + google.protobuf.StringValue address = 8; + google.protobuf.StringValue address2 = 9; + google.protobuf.StringValue zip = 10; + google.protobuf.StringValue phone = 11; + google.protobuf.StringValue email = 12; + google.protobuf.StringValue additionalInfo = 13; } message RelationUpdateMsg { @@ -288,7 +291,7 @@ message RelationUpdateMsg { int64 toIdLSB = 6; string toEntityType = 7; string type = 8; - string typeGroup = 9; + google.protobuf.StringValue typeGroup = 9; string additionalInfo = 10; } @@ -296,13 +299,13 @@ message UserUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - int64 customerIdMSB = 4; - int64 customerIdLSB = 5; + google.protobuf.Int64Value customerIdMSB = 4; + google.protobuf.Int64Value customerIdLSB = 5; string email = 6; string authority = 7; - string firstName = 8; - string lastName = 9; - string additionalInfo = 10; + google.protobuf.StringValue firstName = 8; + google.protobuf.StringValue lastName = 9; + google.protobuf.StringValue additionalInfo = 10; } message WidgetsBundleUpdateMsg { @@ -311,19 +314,19 @@ message WidgetsBundleUpdateMsg { int64 idLSB = 3; string title = 4; string alias = 5; - bytes image = 6; + google.protobuf.BytesValue image = 6; bool isSystem = 7; - string description = 8; + google.protobuf.StringValue description = 8; } message WidgetTypeUpdateMsg { UpdateMsgType msgType = 1; int64 idMSB = 2; int64 idLSB = 3; - string bundleAlias = 4; - string alias = 5; - string name = 6; - string descriptorJson = 7; + google.protobuf.StringValue bundleAlias = 4; + google.protobuf.StringValue alias = 5; + google.protobuf.StringValue name = 6; + google.protobuf.StringValue descriptorJson = 7; bool isSystem = 8; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java index d9f05e3c84..105c624eba 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java @@ -92,6 +92,8 @@ public enum MsgType { DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG, + REMOVE_RPC_TO_DEVICE_ACTOR_MSG, + /** * Message that is sent from the Device Actor to Rule Engine. Requires acknowledgement */ diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/ToDeviceActorNotificationMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/ToDeviceActorNotificationMsg.java similarity index 90% rename from rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/ToDeviceActorNotificationMsg.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/ToDeviceActorNotificationMsg.java index 2f3486b5d4..036ab1b2c5 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/ToDeviceActorNotificationMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/ToDeviceActorNotificationMsg.java @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.rule.engine.api.msg; +package org.thingsboard.server.common.msg; -import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/FromDeviceRpcResponse.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/rpc/FromDeviceRpcResponse.java index b7ce551c0e..06a6cb1d18 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/FromDeviceRpcResponse.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.rpc; +package org.thingsboard.server.common.msg.rpc; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; -import org.thingsboard.rule.engine.api.RpcError; +import org.thingsboard.server.common.data.rpc.RpcError; import java.io.Serializable; import java.util.Optional; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java index b3b33146d0..f9bb2b3810 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.msg.rpc; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -35,5 +36,7 @@ public class ToDeviceRpcRequest implements Serializable { private final long expirationTime; private final ToDeviceRpcRequestBody body; private final boolean persisted; + @JsonIgnore + private final String additionalInfo; } diff --git a/common/pom.xml b/common/pom.xml index 892f4fb582..eea3c0e617 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -41,6 +41,7 @@ queue transport dao-api + cluster-api stats cache coap-server diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 2b1d87fc7b..92e480c660 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -52,6 +52,10 @@ org.thingsboard.common stats
+ + org.thingsboard.common + cluster-api + org.apache.kafka kafka-clients @@ -137,13 +141,4 @@ - - - - org.xolstice.maven.plugins - protobuf-maven-plugin - - - - diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java index 8704631bc6..a5aa451bd1 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java @@ -77,25 +77,34 @@ public class TbKafkaProducerTemplate implements TbQueuePro @Override public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) { - createTopicIfNotExist(tpi); - String key = msg.getKey().toString(); - byte[] data = msg.getData(); - ProducerRecord record; - Iterable
headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList()); - record = new ProducerRecord<>(tpi.getFullTopicName(), null, key, data, headers); - producer.send(record, (metadata, exception) -> { - if (exception == null) { - if (callback != null) { - callback.onSuccess(new KafkaTbQueueMsgMetadata(metadata)); - } - } else { - if (callback != null) { - callback.onFailure(exception); + try { + createTopicIfNotExist(tpi); + String key = msg.getKey().toString(); + byte[] data = msg.getData(); + ProducerRecord record; + Iterable
headers = msg.getHeaders().getData().entrySet().stream().map(e -> new RecordHeader(e.getKey(), e.getValue())).collect(Collectors.toList()); + record = new ProducerRecord<>(tpi.getFullTopicName(), null, key, data, headers); + producer.send(record, (metadata, exception) -> { + if (exception == null) { + if (callback != null) { + callback.onSuccess(new KafkaTbQueueMsgMetadata(metadata)); + } } else { - log.warn("Producer template failure: {}", exception.getMessage(), exception); + if (callback != null) { + callback.onFailure(exception); + } else { + log.warn("Producer template failure: {}", exception.getMessage(), exception); + } } + }); + } catch (Exception e) { + if (callback != null) { + callback.onFailure(e); + } else { + log.warn("Producer template failure (send method wrapper): {}", e.getMessage(), e); } - }); + throw e; + } } private void createTopicIfNotExist(TopicPartitionInfo tpi) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java index 708a6c6168..5dd7c40ba8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java @@ -63,6 +63,12 @@ public class TbKafkaSettings { @Value("${queue.kafka.linger.ms}") private long lingerMs; + @Value("${queue.kafka.max.request.size:1048576}") + private int maxRequestSize; + + @Value("${queue.kafka.max.in.flight.requests.per.connection:5}") + private int maxInFlightRequestsPerConnection; + @Value("${queue.kafka.buffer.memory}") private long bufferMemory; @@ -139,6 +145,8 @@ public class TbKafkaSettings { props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class); props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, compressionType); + props.put(ProducerConfig.MAX_REQUEST_SIZE_CONFIG, maxRequestSize); + props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, maxInFlightRequestsPerConnection); return props; } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java index 265980c0a9..4ea4a167aa 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/client/DefaultCoapClientContext.java @@ -22,6 +22,7 @@ import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.coapserver.CoapServerContext; import org.thingsboard.server.common.data.DataConstants; @@ -74,7 +75,6 @@ import static org.eclipse.californium.core.coap.Message.NONE; @Slf4j @Service -@RequiredArgsConstructor @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") public class DefaultCoapClientContext implements CoapClientContext { @@ -86,6 +86,16 @@ public class DefaultCoapClientContext implements CoapClientContext { private final ConcurrentMap clients = new ConcurrentHashMap<>(); private final ConcurrentMap clientsByToken = new ConcurrentHashMap<>(); + public DefaultCoapClientContext(CoapServerContext config, @Lazy CoapTransportContext transportContext, + TransportService transportService, TransportDeviceProfileCache profileCache, + PartitionService partitionService) { + this.config = config; + this.transportContext = transportContext; + this.transportService = transportService; + this.profileCache = profileCache; + this.partitionService = partitionService; + } + @Override public boolean registerAttributeObservation(TbCoapClientState clientState, String token, CoapExchange exchange) { return registerFeatureObservation(clientState, token, exchange, FeatureType.ATTRIBUTES); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index c2ba0b853b..16f9443547 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -19,6 +19,9 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.leshan.core.model.ObjectLoader; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; @@ -26,6 +29,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigurationAdapter; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -38,6 +42,7 @@ import java.security.KeyStoreException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.util.List; import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; @@ -79,12 +84,14 @@ public class LwM2MTransportBootstrapService { builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); /* Define model provider (Create Models )*/ + List models = ObjectLoader.loadDefault(); + builder.setModel(new StaticModel(models)); /* Create credentials */ this.setServerWithCredentials(builder); -// /** Set securityStore with new ConfigStore */ -// builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); + /* Set securityStore with new ConfigStore */ + builder.setConfigStore(new LwM2MInMemoryBootstrapConfigurationAdapter(lwM2MInMemoryBootstrapConfigStore)); /* SecurityStore */ builder.setSecurityStore(lwM2MBootstrapSecurityStore); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 30ac8e01c3..7dca87458b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -74,15 +74,19 @@ public class LwM2MBootstrapConfig implements Serializable { configBs.servers.put(0, server0); /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ this.bootstrapServer.setBootstrapServerIs(true); - configBs.security.put(0, setServerSecurity(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); + configBs.security.put(0, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */ - configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); + configBs.security.put(1, setServerSecurity(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.getSecurityHost(), this.lwm2mServer.getSecurityPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); return configBs; } - private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { + private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, String securityHost, Integer securityPort, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity(); - serverSecurity.uri = "coaps://" + host + ":" + Integer.toString(port); + if (securityMode.equals(SecurityMode.NO_SEC)) { + serverSecurity.uri = "coap://" + host + ":" + Integer.toString(port); + } else { + serverSecurity.uri = "coaps://" + securityHost + ":" + Integer.toString(securityPort); + } serverSecurity.bootstrapServer = bootstrapServer; serverSecurity.securityMode = securityMode; serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java new file mode 100644 index 0000000000..12325f8c22 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigurationAdapter.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.bootstrap.secure; + +import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; +import org.eclipse.leshan.server.bootstrap.BootstrapConfigurationStoreAdapter; + +public class LwM2MInMemoryBootstrapConfigurationAdapter extends BootstrapConfigurationStoreAdapter { + + public LwM2MInMemoryBootstrapConfigurationAdapter(BootstrapConfigStore store) { + super(store); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java index 27d2e8c865..c8a004f52c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java @@ -31,24 +31,31 @@ public class LwM2MServerBootstrap { String host = "0.0.0.0"; Integer port = 0; + String securityHost = "0.0.0.0"; + Integer securityPort = 0; SecurityMode securityMode = SecurityMode.NO_SEC; Integer serverId = 123; boolean bootstrapServerIs = false; - public LwM2MServerBootstrap(){}; + public LwM2MServerBootstrap() { + } + + ; public LwM2MServerBootstrap(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap profileServerBootstrap) { - this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId(); - this.clientSecretKey = bootstrapFromCredential.getClientSecretKey(); - this.serverPublicKey = profileServerBootstrap.getServerPublicKey(); - this.clientHoldOffTime = profileServerBootstrap.getClientHoldOffTime(); - this.bootstrapServerAccountTimeout = profileServerBootstrap.getBootstrapServerAccountTimeout(); - this.host = (profileServerBootstrap.getHost().equals("0.0.0.0")) ? "localhost" : profileServerBootstrap.getHost(); - this.port = profileServerBootstrap.getPort(); - this.securityMode = profileServerBootstrap.getSecurityMode(); - this.serverId = profileServerBootstrap.getServerId(); - this.bootstrapServerIs = profileServerBootstrap.bootstrapServerIs; + this.clientPublicKeyOrId = bootstrapFromCredential.getClientPublicKeyOrId(); + this.clientSecretKey = bootstrapFromCredential.getClientSecretKey(); + this.serverPublicKey = profileServerBootstrap.getServerPublicKey(); + this.clientHoldOffTime = profileServerBootstrap.getClientHoldOffTime(); + this.bootstrapServerAccountTimeout = profileServerBootstrap.getBootstrapServerAccountTimeout(); + this.host = (profileServerBootstrap.getHost().equals("0.0.0.0")) ? "localhost" : profileServerBootstrap.getHost(); + this.port = profileServerBootstrap.getPort(); + this.securityHost = (profileServerBootstrap.getSecurityHost().equals("0.0.0.0")) ? "localhost" : profileServerBootstrap.getSecurityHost(); + this.securityPort = profileServerBootstrap.getSecurityPort(); + this.securityMode = profileServerBootstrap.getSecurityMode(); + this.serverId = profileServerBootstrap.getServerId(); + this.bootstrapServerIs = profileServerBootstrap.bootstrapServerIs; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 04917baadc..205ac337ad 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -51,6 +51,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; @@ -113,6 +114,9 @@ public class LwM2mClient implements Serializable { private boolean firstEdrxDownlink = true; + @Getter + private final AtomicInteger retryAttempts; + public Object clone() throws CloneNotSupportedException { return super.clone(); } @@ -124,6 +128,7 @@ public class LwM2mClient implements Serializable { this.resources = new ConcurrentHashMap<>(); this.state = LwM2MClientState.CREATED; this.lock = new ReentrantLock(); + this.retryAttempts = new AtomicInteger(0); } public void init(ValidateDeviceCredentialsResponse credentials, UUID sessionId) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 7537e63462..e29dcdd1dd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -93,7 +93,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { log.debug("Fetched clients from store: {}", fetchedClients); fetchedClients.forEach(client -> { lwM2mClientsByEndpoint.put(client.getEndpoint(), client); - updateFetchedClient(nodeId, client); + try { + client.lock(); + updateFetchedClient(nodeId, client); + } finally { + client.unlock(); + } }); } @@ -161,7 +166,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { this.lwM2mClientsByRegistrationId.put(registration.getId(), client); client.setState(LwM2MClientState.REGISTERED); onUplink(client); - if(!compareAndSetSleepFlag(client, false)){ + if (!compareAndSetSleepFlag(client, false)) { clientStore.put(client); } } finally { @@ -311,7 +316,11 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { public void update(LwM2mClient client) { client.lock(); try { - clientStore.put(client); + if (client.getState().equals(LwM2MClientState.REGISTERED)) { + clientStore.put(client); + } else { + log.error("[{}] Client is in invalid state: {}!", client.getEndpoint(), client.getState()); + } } finally { client.unlock(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index d39cfd94f0..2972e26fba 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -45,11 +45,11 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareDeliveryMethod; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; @@ -178,7 +178,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl attributesToFetch.add(SOFTWARE_TAG); attributesToFetch.add(SOFTWARE_URL); } - + var clientSettings = clientContext.getProfile(client.getProfileId()).getClientLwM2mSettings(); onFirmwareStrategyUpdate(client, clientSettings); @@ -283,8 +283,17 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } fwInfo.setUpdateState(state); Optional status = toOtaPackageUpdateStatus(state); - status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, - otaStatus, "Firmware Update State: " + state.name())); + + if (FirmwareUpdateState.IDLE.equals(state) && DOWNLOADING.equals(fwInfo.getStatus())) { + fwInfo.setFailedPackageId(fwInfo.getTargetPackageId()); + status = Optional.of(FAILED); + } + + status.ifPresent(otaStatus -> { + fwInfo.setStatus(otaStatus); + sendStateUpdateToTelemetry(client, fwInfo, + otaStatus, "Firmware Update State: " + state.name()); + }); update(fwInfo); } @@ -294,8 +303,19 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue()); Optional status = toOtaPackageUpdateStatus(result); - status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, - otaStatus, "Firmware Update Result: " + result.name())); + + if (FirmwareUpdateResult.INITIAL.equals(result) && OtaPackageUpdateStatus.UPDATING.equals(fwInfo.getStatus())) { + status = Optional.of(UPDATED); + fwInfo.setRetryAttempts(0); + } + + status.ifPresent(otaStatus -> { + fwInfo.setStatus(otaStatus); + sendStateUpdateToTelemetry(client, fwInfo, + otaStatus, "Firmware Update Result: " + result.name()); + } + ); + if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) { fwInfo.setRetryAttempts(fwInfo.getRetryAttempts() + 1); startFirmwareUpdateIfNeeded(client, fwInfo); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index f7aa5521d0..039629516f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -20,6 +20,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import java.util.Optional; @@ -39,6 +40,7 @@ public abstract class LwM2MClientOtaInfo { protected Strategy strategy; protected State updateState; protected Result result; + protected OtaPackageUpdateStatus status; protected String failedPackageId; protected int retryAttempts; @@ -104,4 +106,8 @@ public abstract class LwM2MClientOtaInfo { public abstract OtaPackageType getType(); + @JsonIgnore + public String getTargetPackageId() { + return getPackageId(targetName, targetVersion); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 1a8d0f8ce3..7ac8026a4c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -77,96 +77,89 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private final LwM2mUplinkMsgHandler uplinkHandler; private final LwM2mDownlinkMsgHandler downlinkHandler; private final LwM2MTelemetryLogService logService; - private final Map rpcSubscriptions = new ConcurrentHashMap<>(); @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest, TransportProtos.SessionInfoProto sessionInfo) { - this.cleanupOldSessions(); - UUID requestUUID = new UUID(rpcRequest.getRequestIdMSB(), rpcRequest.getRequestIdLSB()); log.debug("Received params: {}", rpcRequest.getParams()); - // We use this map to protect from browser issue that the same command is sent twice. - // TODO: This is probably not the best place and should be moved to DeviceActor - if (!this.rpcSubscriptions.containsKey(requestUUID)) { - LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequest.getMethodName()); - if (operationType == null) { - this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED, "Unsupported operation type: " + rpcRequest.getMethodName()); - return; - } - LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); - if (client.getRegistration() == null) { - this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR, "Registration is empty"); - return; - } - try { - if (operationType.isHasObjectId()) { - String objectId = getIdFromParameters(client, rpcRequest); + LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequest.getMethodName()); + if (operationType == null) { + this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED, "Unsupported operation type: " + rpcRequest.getMethodName()); + return; + } + LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); + if (client.getRegistration() == null) { + this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR, "Registration is empty"); + return; + } + try { + if (operationType.isHasObjectId()) { + String objectId = getIdFromParameters(client, rpcRequest); + switch (operationType) { + case READ: + sendReadRequest(client, rpcRequest, objectId); + break; + case OBSERVE: + sendObserveRequest(client, rpcRequest, objectId); + break; + case DISCOVER: + sendDiscoverRequest(client, rpcRequest, objectId); + break; + case EXECUTE: + sendExecuteRequest(client, rpcRequest, objectId); + break; + case WRITE_ATTRIBUTES: + sendWriteAttributesRequest(client, rpcRequest, objectId); + break; + case OBSERVE_CANCEL: + sendCancelObserveRequest(client, rpcRequest, objectId); + break; + case DELETE: + sendDeleteRequest(client, rpcRequest, objectId); + break; + case WRITE_UPDATE: + sendWriteUpdateRequest(client, rpcRequest, objectId); + break; + case WRITE_REPLACE: + sendWriteReplaceRequest(client, rpcRequest, objectId); + break; + default: + throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); + } + } else if (operationType.isComposite()) { + if (clientContext.isComposite(client)) { switch (operationType) { - case READ: - sendReadRequest(client, rpcRequest, objectId); - break; - case OBSERVE: - sendObserveRequest(client, rpcRequest, objectId); - break; - case DISCOVER: - sendDiscoverRequest(client, rpcRequest, objectId); - break; - case EXECUTE: - sendExecuteRequest(client, rpcRequest, objectId); - break; - case WRITE_ATTRIBUTES: - sendWriteAttributesRequest(client, rpcRequest, objectId); - break; - case OBSERVE_CANCEL: - sendCancelObserveRequest(client, rpcRequest, objectId); - break; - case DELETE: - sendDeleteRequest(client, rpcRequest, objectId); - break; - case WRITE_UPDATE: - sendWriteUpdateRequest(client, rpcRequest, objectId); + case READ_COMPOSITE: + sendReadCompositeRequest(client, rpcRequest); break; - case WRITE_REPLACE: - sendWriteReplaceRequest(client, rpcRequest, objectId); + case WRITE_COMPOSITE: + sendWriteCompositeRequest(client, rpcRequest); break; default: throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } - } else if (operationType.isComposite()) { - if (clientContext.isComposite(client)) { - switch (operationType) { - case READ_COMPOSITE: - sendReadCompositeRequest(client, rpcRequest); - break; - case WRITE_COMPOSITE: - sendWriteCompositeRequest(client, rpcRequest); - break; - default: - throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); - } - } else { - this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), - ResponseCode.INTERNAL_SERVER_ERROR, "This device does not support Composite Operation"); - } } else { - switch (operationType) { - case OBSERVE_CANCEL_ALL: - sendCancelAllObserveRequest(client, rpcRequest); - break; - case OBSERVE_READ_ALL: - sendObserveAllRequest(client, rpcRequest); - break; - case DISCOVER_ALL: - sendDiscoverAllRequest(client, rpcRequest); - break; - case FW_UPDATE: - //TODO: implement and add break statement - default: - throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); - } + this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), + ResponseCode.INTERNAL_SERVER_ERROR, "This device does not support Composite Operation"); + } + } else { + switch (operationType) { + case OBSERVE_CANCEL_ALL: + sendCancelAllObserveRequest(client, rpcRequest); + break; + case OBSERVE_READ_ALL: + sendObserveAllRequest(client, rpcRequest); + break; + case DISCOVER_ALL: + sendDiscoverAllRequest(client, rpcRequest); + break; + case FW_UPDATE: + //TODO: implement and add break statement + default: + throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } - } catch (IllegalArgumentException e) { - this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.BAD_REQUEST, e.getMessage()); } + } catch (IllegalArgumentException e) { + this.sendErrorRpcResponse(sessionInfo, rpcRequest.getRequestId(), ResponseCode.BAD_REQUEST, e.getMessage()); } } @@ -318,17 +311,6 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { transportService.process(sessionInfo, msg, null); } - private void cleanupOldSessions() { - log.debug("Before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); - if (rpcSubscriptions.size() > 0) { - long currentTime = System.currentTimeMillis(); - Set rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> currentTime > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); - log.debug("RpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); - rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); - } - log.debug("After rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); - } - @Override public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, TransportProtos.SessionInfoProto sessionInfo) { log.debug("OnToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java index d735eed26e..2a111a9f81 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; +import lombok.extern.slf4j.Slf4j; import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import java.util.ArrayList; @@ -27,6 +29,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +@Slf4j public class TbRedisLwM2MClientStore implements TbLwM2MClientStore { private static final String CLIENT_EP = "CLIENT#EP#"; @@ -76,9 +79,13 @@ public class TbRedisLwM2MClientStore implements TbLwM2MClientStore { @Override public void put(LwM2mClient client) { - byte[] clientSerialized = serializer.asByteArray(client); - try (var connection = connectionFactory.getConnection()) { - connection.getSet(getKey(client.getEndpoint()), clientSerialized); + if (client.getState().equals(LwM2MClientState.UNREGISTERED)) { + log.error("[{}] Client is in invalid state: {}!", client.getEndpoint(), client.getState(), new Exception()); + } else { + byte[] clientSerialized = serializer.asByteArray(client); + try (var connection = connectionFactory.getConnection()) { + connection.getSet(getKey(client.getEndpoint()), clientSerialized); + } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index c81d677155..58201f344e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -110,11 +110,11 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_NAME_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_3_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_NAME_ID; import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; @@ -219,11 +219,16 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl this.initClientTelemetry(lwM2MClient); this.initAttributes(lwM2MClient); otaService.init(lwM2MClient); + lwM2MClient.getRetryAttempts().set(0); } catch (LwM2MClientStateException stateException) { if (LwM2MClientState.UNREGISTERED.equals(stateException.getState())) { log.info("[{}] retry registration due to race condition: [{}].", registration.getEndpoint(), stateException.getState()); // Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again. - onRegistered(registration, previousObservations); + if (lwM2MClient.getRetryAttempts().incrementAndGet() <= 5) { + context.getScheduler().schedule(() -> onRegistered(registration, previousObservations), 1, TimeUnit.SECONDS); + } else { + logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to retry attempts: " + lwM2MClient.getRetryAttempts().get()); + } } else { logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); } diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 676593804e..b6ceb13951 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -88,6 +88,11 @@ junit test + + org.awaitility + awaitility + test + org.mockito mockito-core diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java index f160936c7c..11b46696da 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java @@ -23,10 +23,15 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor; import org.thingsboard.server.transport.mqtt.adaptors.ProtoMqttAdaptor; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.ExecutorService; + /** * Created by ashvayka on 04.10.18. */ @@ -59,4 +64,8 @@ public class MqttTransportContext extends TransportContext { @Setter private SslHandler sslHandler; + @Getter + @Value("${transport.mqtt.msg_queue_size_per_device_limit:100}") + private int messageQueueSizePerDeviceLimit; + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 630aec946d..fa8c2599eb 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -81,6 +81,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; @@ -123,9 +124,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private final SslHandler sslHandler; private final ConcurrentMap mqttQoSMap; - private final DeviceSessionCtx deviceSessionCtx; - private volatile InetSocketAddress address; - private volatile GatewaySessionHandler gatewaySessionHandler; + final DeviceSessionCtx deviceSessionCtx; + volatile InetSocketAddress address; + volatile GatewaySessionHandler gatewaySessionHandler; private final ConcurrentHashMap otaPackSessions; private final ConcurrentHashMap chunkSizes; @@ -153,10 +154,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (message.decoderResult().isSuccess()) { processMqttMsg(ctx, message); } else { - log.error("[{}] Message processing failed: {}", sessionId, message.decoderResult().cause().getMessage()); + log.error("[{}] Message decoding failed: {}", sessionId, message.decoderResult().cause().getMessage()); ctx.close(); } } else { + log.debug("[{}] Received non mqtt message: {}", sessionId, msg.getClass().getSimpleName()); ctx.close(); } } finally { @@ -164,11 +166,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) { - address = (InetSocketAddress) ctx.channel().remoteAddress(); + void processMqttMsg(ChannelHandlerContext ctx, MqttMessage msg) { + address = getAddress(ctx); if (msg.fixedHeader() == null) { log.info("[{}:{}] Invalid message received", address.getHostName(), address.getPort()); - processDisconnect(ctx); + ctx.close(); return; } deviceSessionCtx.setChannel(ctx); @@ -177,10 +179,14 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else if (deviceSessionCtx.isProvisionOnly()) { processProvisionSessionMsg(ctx, msg); } else { - processRegularSessionMsg(ctx, msg); + enqueueRegularSessionMsg(ctx, msg); } } + InetSocketAddress getAddress(ChannelHandlerContext ctx) { + return (InetSocketAddress) ctx.channel().remoteAddress(); + } + private void processProvisionSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { case PUBLISH: @@ -204,8 +210,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } } else { + log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName); ctx.close(); - throw new RuntimeException("Unsupported topic for provisioning requests!"); } } catch (RuntimeException | AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); @@ -216,14 +222,33 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); break; case DISCONNECT: - if (checkConnected(ctx, msg)) { - processDisconnect(ctx); - } + ctx.close(); break; } } - private void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { + void enqueueRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { + final int queueSize = deviceSessionCtx.getMsgQueueSize(); + if (queueSize >= context.getMessageQueueSizePerDeviceLimit()) { + log.info("Closing current session because msq queue size for device {} exceed limit {} with msgQueueSize counter {} and actual queue size {}", + deviceSessionCtx.getDeviceId(), context.getMessageQueueSizePerDeviceLimit(), queueSize, deviceSessionCtx.getMsgQueueSize()); + ctx.close(); + return; + } + + deviceSessionCtx.addToQueue(msg); + processMsgQueue(ctx); //Under the normal conditions the msg queue will contain 0 messages. Many messages will be processed on device connect event in separate thread pool + } + + void processMsgQueue(ChannelHandlerContext ctx) { + if (!deviceSessionCtx.isConnected()) { + log.trace("[{}][{}] Postpone processing msg due to device is not connected. Msg queue size is {}", sessionId, deviceSessionCtx.getDeviceId(), deviceSessionCtx.getMsgQueueSize()); + return; + } + deviceSessionCtx.tryProcessQueuedMsgs(msg -> processRegularSessionMsg(ctx, msg)); + } + + void processRegularSessionMsg(ChannelHandlerContext ctx, MqttMessage msg) { switch (msg.fixedHeader().messageType()) { case PUBLISH: processPublish(ctx, (MqttPublishMessage) msg); @@ -241,9 +266,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } break; case DISCONNECT: - if (checkConnected(ctx, msg)) { - processDisconnect(ctx); - } + ctx.close(); break; case PUBACK: int msgId = ((MqttPubAckMessage) msg).variableHeader().messageId(); @@ -304,6 +327,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } catch (RuntimeException | AdaptorException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + ctx.close(); } } @@ -396,7 +420,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); - processDisconnect(ctx); + ctx.close(); } }; } @@ -422,7 +446,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else { deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); } - scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); + scheduler.schedule((Callable) ctx::close, 60, TimeUnit.SECONDS); } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } @@ -431,7 +455,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { log.trace("[{}] Failed to publish msg: {}", sessionId, msg, e); - processDisconnect(ctx); + ctx.close(); } } @@ -466,7 +490,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e); - processDisconnect(ctx); + ctx.close(); } } @@ -488,7 +512,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx .getPayloadAdaptor() .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes())); - processDisconnect(ctx); + ctx.close(); } private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { @@ -588,7 +612,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader); } - private void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { + void processConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { log.info("[{}] Processing connect msg for client: {}!", sessionId, msg.payload().clientIdentifier()); String userName = msg.payload().userName(); String clientId = msg.payload().clientIdentifier(); @@ -657,6 +681,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement }); } catch (Exception e) { ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_NOT_AUTHORIZED, connectMessage)); + log.trace("[{}] X509 auth failure: {}", sessionId, address, e); ctx.close(); } } @@ -674,12 +699,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement return null; } - private void processDisconnect(ChannelHandlerContext ctx) { - ctx.close(); - log.info("[{}] Client disconnected!", sessionId); - doDisconnect(); - } - private MqttConnAckMessage createMqttConnAckMsg(MqttConnectReturnCode returnCode, MqttConnectMessage msg) { MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(CONNACK, false, AT_MOST_ONCE, false, 0); @@ -724,7 +743,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement return true; } else { log.info("[{}] Closing current session due to invalid msg order: {}", sessionId, msg); - ctx.close(); return false; } } @@ -749,11 +767,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void operationComplete(Future future) throws Exception { + log.trace("[{}] Channel closed!", sessionId); doDisconnect(); } - private void doDisconnect() { + public void doDisconnect() { if (deviceSessionCtx.isConnected()) { + log.info("[{}] Client disconnected!", sessionId); transportService.process(deviceSessionCtx.getSessionInfo(), DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); transportService.deregisterSession(deviceSessionCtx.getSessionInfo()); if (gatewaySessionHandler != null) { @@ -761,6 +781,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } deviceSessionCtx.setDisconnected(); } + deviceSessionCtx.release(); } @@ -778,7 +799,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement SessionMetaData sessionMetaData = transportService.registerAsyncSession(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.this); checkGatewaySession(sessionMetaData); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED, connectMessage)); + deviceSessionCtx.setConnected(true); log.info("[{}] Client connected!", sessionId); + transportService.getCallbackExecutor().execute(() -> processMsgQueue(ctx)); //this callback will execute in Producer worker thread and hard or blocking work have to be submitted to the separate thread. } @Override @@ -817,7 +840,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); - processDisconnect(deviceSessionCtx.getChannel()); + deviceSessionCtx.getChannel().close(); } @Override diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java index 3804d96cf6..ab11d859b8 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java @@ -18,6 +18,8 @@ package org.thingsboard.server.transport.mqtt.session; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.util.ReferenceCountUtil; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -34,9 +36,16 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilter; import org.thingsboard.server.transport.mqtt.util.MqttTopicFilterFactory; +import java.util.Collection; +import java.util.Collections; +import java.util.Queue; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; /** * @author Andrew Shvayka @@ -45,13 +54,21 @@ import java.util.concurrent.atomic.AtomicInteger; public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { @Getter + @Setter private ChannelHandlerContext channel; @Getter - private MqttTransportContext context; + private final MqttTransportContext context; private final AtomicInteger msgIdSeq = new AtomicInteger(0); + private final ConcurrentLinkedQueue msgQueue = new ConcurrentLinkedQueue<>(); + + @Getter + private final Lock msgQueueProcessorLock = new ReentrantLock(); + + private final AtomicInteger msgQueueSize = new AtomicInteger(0); + @Getter @Setter private boolean provisionOnly = false; @@ -73,10 +90,6 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { this.context = context; } - public void setChannel(ChannelHandlerContext channel) { - this.channel = channel; - } - public int nextMsgId() { return msgIdSeq.incrementAndGet(); } @@ -150,4 +163,49 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { rpcResponseDynamicMessageDescriptor = protoTransportPayloadConfig.getRpcResponseDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceRpcResponseProtoSchema()); rpcRequestDynamicMessageBuilder = protoTransportPayloadConfig.getRpcRequestDynamicMessageBuilder(protoTransportPayloadConfig.getDeviceRpcRequestProtoSchema()); } + + public void addToQueue(MqttMessage msg) { + msgQueueSize.incrementAndGet(); + ReferenceCountUtil.retain(msg); + msgQueue.add(msg); + } + + public void tryProcessQueuedMsgs(Consumer msgProcessor) { + while (!msgQueue.isEmpty()) { + if (msgQueueProcessorLock.tryLock()) { + try { + MqttMessage msg; + while ((msg = msgQueue.poll()) != null) { + try { + msgQueueSize.decrementAndGet(); + msgProcessor.accept(msg); + } finally { + ReferenceCountUtil.safeRelease(msg); + } + } + } finally { + msgQueueProcessorLock.unlock(); + } + } else { + return; + } + } + } + + public int getMsgQueueSize() { + return msgQueueSize.get(); + } + + public void release() { + if (!msgQueue.isEmpty()) { + log.warn("doDisconnect for device {} but unprocessed messages {} left in the msg queue", getDeviceId(), msgQueue.size()); + msgQueue.forEach(ReferenceCountUtil::safeRelease); + msgQueue.clear(); + } + } + + public Collection getMsgQueueSnapshot(){ + return Collections.unmodifiableCollection(msgQueue); + } + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java index 3fed5e51ea..21086d75d8 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java @@ -60,6 +60,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple .setDeviceProfileIdLSB(deviceInfo.getDeviceProfileId().getId().getLeastSignificantBits()) .build()); setDeviceInfo(deviceInfo); + setConnected(true); setDeviceProfile(deviceProfile); this.transportService = transportService; } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java index 193a7d24f5..7b45b5f9d9 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java @@ -34,6 +34,7 @@ import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; +import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.TransportService; @@ -66,6 +67,8 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import static org.springframework.util.ConcurrentReferenceHashMap.ReferenceType; + /** * Created by ashvayka on 19.01.17. */ @@ -82,7 +85,7 @@ public class GatewaySessionHandler { private final UUID sessionId; private final ConcurrentMap deviceCreationLockMap; private final ConcurrentMap devices; - private final ConcurrentMap> deviceFutures; + private final ConcurrentMap> deviceFutures; private final ConcurrentMap mqttQoSMap; private final ChannelHandlerContext channel; private final DeviceSessionCtx deviceSessionCtx; @@ -95,11 +98,15 @@ public class GatewaySessionHandler { this.sessionId = sessionId; this.devices = new ConcurrentHashMap<>(); this.deviceFutures = new ConcurrentHashMap<>(); - this.deviceCreationLockMap = new ConcurrentHashMap<>(); + this.deviceCreationLockMap = createWeakMap(); this.mqttQoSMap = deviceSessionCtx.getMqttQoSMap(); this.channel = deviceSessionCtx.getChannel(); } + ConcurrentReferenceHashMap createWeakMap() { + return new ConcurrentReferenceHashMap<>(16, ReferenceType.WEAK); + } + public void onDeviceConnect(MqttPublishMessage mqttMsg) throws AdaptorException { if (isJsonPayloadType()) { onDeviceConnectJson(mqttMsg); @@ -228,21 +235,22 @@ public class GatewaySessionHandler { if (result == null) { return getDeviceCreationFuture(deviceName, deviceType); } else { - return toCompletedFuture(result); + return Futures.immediateFuture(result); } } finally { deviceCreationLock.unlock(); } } else { - return toCompletedFuture(result); + return Futures.immediateFuture(result); } } private ListenableFuture getDeviceCreationFuture(String deviceName, String deviceType) { - SettableFuture future = deviceFutures.get(deviceName); - if (future == null) { - final SettableFuture futureToSet = SettableFuture.create(); - deviceFutures.put(deviceName, futureToSet); + final SettableFuture futureToSet = SettableFuture.create(); + ListenableFuture future = deviceFutures.putIfAbsent(deviceName, futureToSet); + if (future != null) { + return future; + } try { transportService.process(GetOrCreateDeviceFromGatewayRequestMsg.newBuilder() .setDeviceName(deviceName) @@ -282,15 +290,6 @@ public class GatewaySessionHandler { deviceFutures.remove(deviceName); throw e; } - } else { - return future; - } - } - - private ListenableFuture toCompletedFuture(GatewayDeviceSessionCtx result) { - SettableFuture future = SettableFuture.create(); - future.set(result); - return future; } private int getMsgId(MqttPublishMessage mqttMsg) { @@ -342,7 +341,7 @@ public class GatewaySessionHandler { for (Map.Entry deviceEntry : jsonObj.entrySet()) { String deviceName = deviceEntry.getKey(); Futures.addCallback(checkDeviceConnected(deviceName), - new FutureCallback() { + new FutureCallback<>() { @Override public void onSuccess(@Nullable GatewayDeviceSessionCtx deviceCtx) { if (!deviceEntry.getValue().isJsonArray()) { @@ -353,6 +352,7 @@ public class GatewaySessionHandler { processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId); } catch (Throwable e) { log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, deviceEntry.getValue(), e); + channel.close(); } } @@ -384,6 +384,7 @@ public class GatewaySessionHandler { processPostTelemetryMsg(deviceCtx, postTelemetryMsg, deviceName, msgId); } catch (Throwable e) { log.warn("[{}][{}] Failed to convert telemetry: {}", gateway.getDeviceId(), deviceName, msg, e); + channel.close(); } } diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/MqttTransportHandlerTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/MqttTransportHandlerTest.java new file mode 100644 index 0000000000..a9903fe867 --- /dev/null +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/MqttTransportHandlerTest.java @@ -0,0 +1,209 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.mqtt; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.EmptyByteBuf; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttConnectMessage; +import io.netty.handler.codec.mqtt.MqttConnectPayload; +import io.netty.handler.codec.mqtt.MqttConnectVariableHeader; +import io.netty.handler.codec.mqtt.MqttFixedHeader; +import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageType; +import io.netty.handler.codec.mqtt.MqttPublishMessage; +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; +import io.netty.handler.codec.mqtt.MqttQoS; +import io.netty.handler.ssl.SslHandler; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.ThingsBoardThreadFactory; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +@Slf4j +@RunWith(MockitoJUnitRunner.class) +public class MqttTransportHandlerTest { + + public static final int MSG_QUEUE_LIMIT = 10; + public static final InetSocketAddress IP_ADDR = new InetSocketAddress("127.0.0.1", 9876); + public static final int TIMEOUT = 30; + + @Mock + MqttTransportContext context; + @Mock + SslHandler sslHandler; + @Mock + ChannelHandlerContext ctx; + + AtomicInteger packedId = new AtomicInteger(); + ExecutorService executor; + MqttTransportHandler handler; + + @Before + public void setUp() throws Exception { + willReturn(MSG_QUEUE_LIMIT).given(context).getMessageQueueSizePerDeviceLimit(); + + handler = spy(new MqttTransportHandler(context, sslHandler)); + willReturn(IP_ADDR).given(handler).getAddress(any()); + } + + @After + public void tearDown() { + if (executor != null) { + executor.shutdownNow(); + } + } + + MqttConnectMessage getMqttConnectMessage() { + MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.CONNECT, true, MqttQoS.AT_LEAST_ONCE, false, 123); + MqttConnectVariableHeader variableHeader = new MqttConnectVariableHeader("device", packedId.incrementAndGet(), true, true, true, 1, true, false, 60); + MqttConnectPayload payload = new MqttConnectPayload("clientId", "topic", "message".getBytes(StandardCharsets.UTF_8), "username", "password".getBytes(StandardCharsets.UTF_8)); + return new MqttConnectMessage(mqttFixedHeader, variableHeader, payload); + } + + MqttPublishMessage getMqttPublishMessage() { + MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, true, MqttQoS.AT_LEAST_ONCE, false, 123); + MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader("v1/gateway/telemetry", packedId.incrementAndGet()); + ByteBuf payload = new EmptyByteBuf(new PooledByteBufAllocator()); + return new MqttPublishMessage(mqttFixedHeader, variableHeader, payload); + } + + @Test + public void givenMqttConnectMessage_whenProcessMqttMsg_thenProcessConnect() { + MqttConnectMessage msg = getMqttConnectMessage(); + willDoNothing().given(handler).processConnect(ctx, msg); + + handler.processMqttMsg(ctx, msg); + + assertThat(handler.address, is(IP_ADDR)); + assertThat(handler.deviceSessionCtx.getChannel(), is(ctx)); + verify(handler, never()).doDisconnect(); + verify(handler, times(1)).processConnect(ctx, msg); + } + + @Test + public void givenQueueLimit_whenEnqueueRegularSessionMsgOverLimit_thenOK() { + List messages = Stream.generate(this::getMqttPublishMessage).limit(MSG_QUEUE_LIMIT).collect(Collectors.toList()); + messages.forEach(msg -> handler.enqueueRegularSessionMsg(ctx, msg)); + assertThat(handler.deviceSessionCtx.getMsgQueueSize(), is(MSG_QUEUE_LIMIT)); + assertThat(handler.deviceSessionCtx.getMsgQueueSnapshot(), contains(messages.toArray())); + } + + @Test + public void givenQueueLimit_whenEnqueueRegularSessionMsgOverLimit_thenCtxClose() { + final int limit = MSG_QUEUE_LIMIT + 1; + willDoNothing().given(handler).processMsgQueue(ctx); + List messages = Stream.generate(this::getMqttPublishMessage).limit(limit).collect(Collectors.toList()); + + messages.forEach((msg) -> handler.enqueueRegularSessionMsg(ctx, msg)); + + assertThat(handler.deviceSessionCtx.getMsgQueueSize(), is(MSG_QUEUE_LIMIT)); + verify(handler, times(limit)).enqueueRegularSessionMsg(any(), any()); + verify(handler, times(MSG_QUEUE_LIMIT)).processMsgQueue(any()); + verify(ctx, times(1)).close(); + } + + @Test + public void givenMqttConnectMessageAndPublishImmediately_whenProcessMqttMsg_thenEnqueueRegularSessionMsg() { + givenMqttConnectMessage_whenProcessMqttMsg_thenProcessConnect(); + + List messages = Stream.generate(this::getMqttPublishMessage).limit(MSG_QUEUE_LIMIT).collect(Collectors.toList()); + + messages.forEach((msg) -> handler.processMqttMsg(ctx, msg)); + + assertThat(handler.address, is(IP_ADDR)); + assertThat(handler.deviceSessionCtx.getChannel(), is(ctx)); + assertThat(handler.deviceSessionCtx.isConnected(), is(false)); + assertThat(handler.deviceSessionCtx.getMsgQueueSize(), is(MSG_QUEUE_LIMIT)); + assertThat(handler.deviceSessionCtx.getMsgQueueSnapshot(), contains(messages.toArray())); + verify(handler, never()).doDisconnect(); + verify(handler, times(1)).processConnect(any(), any()); + verify(handler, times(MSG_QUEUE_LIMIT)).enqueueRegularSessionMsg(any(), any()); + verify(handler, never()).processRegularSessionMsg(any(), any()); + messages.forEach((msg) -> verify(handler, times(1)).enqueueRegularSessionMsg(ctx, msg)); + } + + @Test + public void givenMessageQueue_whenProcessMqttMsgConcurrently_thenEnqueueRegularSessionMsg() throws InterruptedException { + //given + assertThat(handler.deviceSessionCtx.isConnected(), is(false)); + assertThat(MSG_QUEUE_LIMIT, greaterThan(2)); + List messages = Stream.generate(this::getMqttPublishMessage).limit(MSG_QUEUE_LIMIT).collect(Collectors.toList()); + messages.forEach((msg) -> handler.enqueueRegularSessionMsg(ctx, msg)); + willDoNothing().given(handler).processRegularSessionMsg(any(), any()); + executor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName(getClass().getName())); + + CountDownLatch readyLatch = new CountDownLatch(MSG_QUEUE_LIMIT); + CountDownLatch startLatch = new CountDownLatch(1); + CountDownLatch finishLatch = new CountDownLatch(MSG_QUEUE_LIMIT); + + Stream.iterate(0, i -> i + 1).limit(MSG_QUEUE_LIMIT).forEach(x -> + executor.submit(() -> { + try { + readyLatch.countDown(); + assertThat(startLatch.await(TIMEOUT, TimeUnit.SECONDS), is(true)); + handler.processMsgQueue(ctx); + finishLatch.countDown(); + } catch (Exception e) { + log.error("Failed to run processMsgQueue", e); + fail("Failed to run processMsgQueue"); + } + })); + + //when + assertThat(readyLatch.await(TIMEOUT, TimeUnit.SECONDS), is(true)); + handler.deviceSessionCtx.setConnected(true); + startLatch.countDown(); + assertThat(finishLatch.await(TIMEOUT, TimeUnit.SECONDS), is(true)); + + //then + assertThat(handler.deviceSessionCtx.getMsgQueueSize(), is(0)); + assertThat(handler.deviceSessionCtx.getMsgQueueSnapshot(), empty()); + verify(handler, times(MSG_QUEUE_LIMIT)).processRegularSessionMsg(any(), any()); + messages.forEach((msg) -> verify(handler, times(1)).processRegularSessionMsg(ctx, msg)); + } + +} \ No newline at end of file diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandlerTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandlerTest.java new file mode 100644 index 0000000000..ed45e9e44e --- /dev/null +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandlerTest.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.mqtt.session; + +import org.junit.Test; + +import java.util.WeakHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertTrue; +import static org.mockito.BDDMockito.willCallRealMethod; +import static org.mockito.Mockito.mock; + +public class GatewaySessionHandlerTest { + + @Test + public void givenWeakHashMap_WhenGC_thenMapIsEmpty() { + WeakHashMap map = new WeakHashMap<>(); + + String deviceName = new String("device"); //constants are static and doesn't affected by GC, so use new instead + map.put(deviceName, new ReentrantLock()); + assertTrue(map.containsKey(deviceName)); + + deviceName = null; + System.gc(); + + await().atMost(10, TimeUnit.SECONDS).until(() -> !map.containsKey("device")); + } + + @Test + public void givenConcurrentReferenceHashMap_WhenGC_thenMapIsEmpty() { + GatewaySessionHandler gsh = mock(GatewaySessionHandler.class); + willCallRealMethod().given(gsh).createWeakMap(); + + ConcurrentMap map = gsh.createWeakMap(); + map.put("device", new ReentrantLock()); + assertTrue(map.containsKey("device")); + + System.gc(); + + await().atMost(10, TimeUnit.SECONDS).until(() -> !map.containsKey("device")); + } + +} \ No newline at end of file diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 0efde4d893..bc234340cb 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -188,6 +188,7 @@ public class SnmpTransportContext extends TransportContext { deviceSessionContext.setSessionInfo(sessionInfo); deviceSessionContext.setDeviceInfo(msg.getDeviceInfo()); + deviceSessionContext.setConnected(true); } else { log.warn("[{}] Failed to process device auth", deviceSessionContext.getDeviceId()); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 5f2fa4f197..237954c553 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -56,6 +56,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MC import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import java.util.concurrent.ExecutorService; + /** * Created by ashvayka on 04.10.18. */ @@ -131,4 +133,6 @@ public interface TransportService { void log(SessionInfoProto sessionInfo, String msg); void notifyAboutUplink(SessionInfoProto sessionInfo, TransportProtos.UplinkNotificationMsg build, TransportServiceCallback empty); + + ExecutorService getCallbackExecutor(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index e3390eb9bb..c705d8364a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -1141,4 +1141,9 @@ public class DefaultTransportService implements TransportService { callback.onError(e); } } + + @Override + public ExecutorService getCallbackExecutor() { + return transportCallbackExecutor; + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 7d5bc281ec..1d2b7382d0 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -46,6 +46,7 @@ public abstract class DeviceAwareSessionContext implements SessionContext { @Setter private volatile TransportProtos.SessionInfoProto sessionInfo; + @Setter private volatile boolean connected; public DeviceId getDeviceId() { @@ -54,7 +55,6 @@ public abstract class DeviceAwareSessionContext implements SessionContext { public void setDeviceInfo(TransportDeviceInfo deviceInfo) { this.deviceInfo = deviceInfo; - this.connected = true; this.deviceId = deviceInfo.getDeviceId(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index b9a2e46f37..8d012f63a4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -15,9 +15,7 @@ */ package org.thingsboard.server.dao.edge; -import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -36,9 +34,9 @@ public class BaseEdgeEventService implements EdgeEventService { private EdgeEventDao edgeEventDao; @Override - public ListenableFuture saveAsync(EdgeEvent edgeEvent) { + public EdgeEvent save(EdgeEvent edgeEvent) { edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); - return edgeEventDao.saveAsync(edgeEvent); + return edgeEventDao.save(edgeEvent); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java index 804703be76..92e0685105 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java @@ -30,12 +30,12 @@ import java.util.UUID; public interface EdgeEventDao extends Dao { /** - * Save or update edge event object async + * Save or update edge event object * * @param edgeEvent the event object * @return saved edge event object future */ - ListenableFuture saveAsync(EdgeEvent edgeEvent); + EdgeEvent save(EdgeEvent edgeEvent); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 525630fc27..e8e90f2d3c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -519,6 +519,7 @@ public class ModelConstants { public static final String RPC_REQUEST = "request"; public static final String RPC_RESPONSE = "response"; public static final String RPC_STATUS = "status"; + public static final String RPC_ADDITIONAL_INFO = ADDITIONAL_INFO_PROPERTY; /** * Edge constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java index a8823cb8cd..d68aa1afb8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java @@ -36,6 +36,7 @@ import javax.persistence.Enumerated; import javax.persistence.Table; import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.RPC_ADDITIONAL_INFO; import static org.thingsboard.server.dao.model.ModelConstants.RPC_DEVICE_ID; import static org.thingsboard.server.dao.model.ModelConstants.RPC_EXPIRATION_TIME; import static org.thingsboard.server.dao.model.ModelConstants.RPC_REQUEST; @@ -72,6 +73,10 @@ public class RpcEntity extends BaseSqlEntity implements BaseEntity { @Column(name = RPC_STATUS) private RpcStatus status; + @Type(type = "json") + @Column(name = RPC_ADDITIONAL_INFO) + private JsonNode additionalInfo; + public RpcEntity() { super(); } @@ -85,6 +90,7 @@ public class RpcEntity extends BaseSqlEntity implements BaseEntity { this.request = rpc.getRequest(); this.response = rpc.getResponse(); this.status = rpc.getStatus(); + this.additionalInfo = rpc.getAdditionalInfo(); } @Override @@ -97,6 +103,7 @@ public class RpcEntity extends BaseSqlEntity implements BaseEntity { rpc.setRequest(request); rpc.setResponse(response); rpc.setStatus(status); + rpc.setAdditionalInfo(additionalInfo); return rpc; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java index 378e7d03bb..13a67c0161 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java @@ -52,7 +52,7 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) { log.trace("Executing saveAdminSettings [{}]", adminSettings); adminSettingsValidator.validate(adminSettings, data -> tenantId); - if (adminSettings.getKey().equals("mail") && "".equals(adminSettings.getJsonValue().get("password").asText())) { + if(adminSettings.getKey().equals("mail") && !adminSettings.getJsonValue().has("password")) { AdminSettings mailSettings = findAdminSettingsByKey(tenantId, "mail"); if (mailSettings != null) { ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText()); @@ -61,7 +61,7 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { return adminSettingsDao.save(tenantId, adminSettings); } - + private DataValidator adminSettingsValidator = new DataValidator() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java index fc4250044d..050d0181f0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java @@ -41,6 +41,10 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -50,6 +54,8 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao readWriteLocks = new ConcurrentHashMap<>(); + @Autowired private EdgeEventRepository edgeEventRepository; @@ -64,47 +70,59 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao saveAsync(EdgeEvent edgeEvent) { - log.debug("Save edge event [{}] ", edgeEvent); - if (edgeEvent.getId() == null) { - UUID timeBased = Uuids.timeBased(); - edgeEvent.setId(new EdgeEventId(timeBased)); - edgeEvent.setCreatedTime(Uuids.unixTimestamp(timeBased)); - } else if (edgeEvent.getCreatedTime() == 0L) { - UUID eventId = edgeEvent.getId().getId(); - if (eventId.version() == 1) { - edgeEvent.setCreatedTime(Uuids.unixTimestamp(eventId)); - } else { - edgeEvent.setCreatedTime(System.currentTimeMillis()); + public EdgeEvent save(EdgeEvent edgeEvent) { + final Lock readWriteLock = readWriteLocks.computeIfAbsent(edgeEvent.getEdgeId(), id -> new ReentrantLock()); + readWriteLock.lock(); + try { + log.debug("Save edge event [{}] ", edgeEvent); + if (edgeEvent.getId() == null) { + UUID timeBased = Uuids.timeBased(); + edgeEvent.setId(new EdgeEventId(timeBased)); + edgeEvent.setCreatedTime(Uuids.unixTimestamp(timeBased)); + } else if (edgeEvent.getCreatedTime() == 0L) { + UUID eventId = edgeEvent.getId().getId(); + if (eventId.version() == 1) { + edgeEvent.setCreatedTime(Uuids.unixTimestamp(eventId)); + } else { + edgeEvent.setCreatedTime(System.currentTimeMillis()); + } } + if (StringUtils.isEmpty(edgeEvent.getUid())) { + edgeEvent.setUid(edgeEvent.getId().toString()); + } + return save(new EdgeEventEntity(edgeEvent)).orElse(null); + } finally { + readWriteLock.unlock(); } - if (StringUtils.isEmpty(edgeEvent.getUid())) { - edgeEvent.setUid(edgeEvent.getId().toString()); - } - return service.submit(() -> save(new EdgeEventEntity(edgeEvent)).orElse(null)); } @Override public PageData findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) { - if (withTsUpdate) { - return DaoUtil.toPageData( - edgeEventRepository - .findEdgeEventsByTenantIdAndEdgeId( - tenantId, - edgeId.getId(), - pageLink.getStartTime(), - pageLink.getEndTime(), - DaoUtil.toPageable(pageLink))); - } else { - return DaoUtil.toPageData( - edgeEventRepository - .findEdgeEventsByTenantIdAndEdgeIdWithoutTimeseriesUpdated( - tenantId, - edgeId.getId(), - pageLink.getStartTime(), - pageLink.getEndTime(), - DaoUtil.toPageable(pageLink))); + final Lock readWriteLock = readWriteLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); + readWriteLock.lock(); + try { + if (withTsUpdate) { + return DaoUtil.toPageData( + edgeEventRepository + .findEdgeEventsByTenantIdAndEdgeId( + tenantId, + edgeId.getId(), + pageLink.getStartTime(), + pageLink.getEndTime(), + DaoUtil.toPageable(pageLink))); + } else { + return DaoUtil.toPageData( + edgeEventRepository + .findEdgeEventsByTenantIdAndEdgeIdWithoutTimeseriesUpdated( + tenantId, + edgeId.getId(), + pageLink.getStartTime(), + pageLink.getEndTime(), + DaoUtil.toPageable(pageLink))); + } + } finally { + readWriteLock.unlock(); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index bca52d3ad4..9c2dce109b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -100,7 +100,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key) { return Futures.immediateFuture(null); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 31f3407fbf..435c9c5b70 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -124,7 +124,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key) { return Futures.immediateFuture(0); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index fb15af723a..3170f6c256 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -170,7 +170,7 @@ public class BaseTimeseriesService implements TimeseriesService { if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Read only"); } - futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl)); + futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey())); futures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), v -> 0, MoreExecutors.directExecutor())); futures.add(timeseriesDao.save(tenantId, entityId, tsKvEntry, ttl)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index ce653e2e6e..736b234eb9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -181,11 +181,14 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key) { if (isFixedPartitioning()) { return Futures.immediateFuture(null); } - ttl = computeTtl(ttl); + // DO NOT apply custom TTL to partition, otherwise, short TTL will remove partition too early + // partitions must remain in the DB forever or be removed only by systemTtl + // removal of empty partition is too expensive (we need to scan all data keys for these partitions with ALLOW FILTERING) + long ttl = computeTtl(0); long partition = toPartitionTs(tsKvEntryTs); if (cassandraTsPartitionsCache == null) { return doSavePartition(tenantId, entityId, key, ttl, partition); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java index e9af5f0b75..5700410fbb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java @@ -33,7 +33,7 @@ public interface TimeseriesDao { ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl); - ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl); + ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key); ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index f1180748c8..e58ca10459 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -582,5 +582,6 @@ CREATE TABLE IF NOT EXISTS rpc ( expiration_time bigint NOT NULL, request varchar(10000000) NOT NULL, response varchar(10000000), + additional_info varchar(10000000), status varchar(255) NOT NULL ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 68d6521167..69760c377a 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -616,6 +616,7 @@ CREATE TABLE IF NOT EXISTS rpc ( expiration_time bigint NOT NULL, request varchar(10000000) NOT NULL, response varchar(10000000), + additional_info varchar(10000000), status varchar(255) NOT NULL ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java index d3c6c97367..e8eae60131 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/nosql/CassandraPartitionsCacheTest.java @@ -100,10 +100,10 @@ public class CassandraPartitionsCacheTest { long tsKvEntryTs = System.currentTimeMillis(); for (int i = 0; i < 50000; i++) { - cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i, 0); + cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i); } for (int i = 0; i < 60000; i++) { - cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i, 0); + cassandraBaseTimeseriesDao.savePartition(tenantId, tenantId, tsKvEntryTs, "test" + i); } verify(cassandraBaseTimeseriesDao, times(60000)).executeAsyncWrite(any(TenantId.class), any(Statement.class)); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java index a7a67a8f4b..7c86e38eec 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeEventServiceTest.java @@ -42,7 +42,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { EdgeId edgeId = new EdgeId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased()); EdgeEvent edgeEvent = generateEdgeEvent(null, edgeId, deviceId, EdgeEventActionType.ADDED); - EdgeEvent saved = edgeEventService.saveAsync(edgeEvent).get(); + EdgeEvent saved = edgeEventService.save(edgeEvent); Assert.assertEquals(saved.getTenantId(), edgeEvent.getTenantId()); Assert.assertEquals(saved.getEdgeId(), edgeEvent.getEdgeId()); Assert.assertEquals(saved.getEntityId(), edgeEvent.getEntityId()); @@ -109,7 +109,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { TimePageLink pageLink = new TimePageLink(1); EdgeEvent edgeEventWithTsUpdate = generateEdgeEvent(tenantId, edgeId, deviceId, EdgeEventActionType.TIMESERIES_UPDATED); - edgeEventService.saveAsync(edgeEventWithTsUpdate).get(); + edgeEventService.save(edgeEventWithTsUpdate); PageData allEdgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true); PageData edgeEventsWithoutTsUpdate = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false); @@ -124,6 +124,6 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { private EdgeEvent saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception { EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, entityId, EdgeEventActionType.ADDED); edgeEvent.setId(new EdgeEventId(Uuids.startOf(time))); - return edgeEventService.saveAsync(edgeEvent).get(); + return edgeEventService.save(edgeEvent); } } \ No newline at end of file diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index c8156b6ef1..7ae8f863af 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -267,7 +267,7 @@ public class MqttClientTest extends AbstractContainerTest { ListenableFuture future = service.submit(() -> { try { return restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}", + .postForEntity(HTTPS_URL + "/api/rpc/twoway/{deviceId}", mapper.readTree(serverRpcPayload.toString()), String.class, device.getId()); } catch (IOException e) { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java index a059957d6b..def365dcb1 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java @@ -263,7 +263,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { ListenableFuture future = service.submit(() -> { try { return restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}", + .postForEntity(HTTPS_URL + "/api/rpc/twoway/{deviceId}", mapper.readTree(serverRpcPayload.toString()), String.class, createdDevice.getId()); } catch (IOException e) { diff --git a/pom.xml b/pom.xml index d0c9864ef3..b6d460acd5 100755 --- a/pom.xml +++ b/pom.xml @@ -42,13 +42,14 @@ 2.3.12.RELEASE 5.2.16.RELEASE 5.2.11.RELEASE - 5.4.1 + 5.4.4 2.4.3 3.3.0 0.7.0 2.2.0 4.12 5.7.1 + 4.1.0 2.2 1.7.7 1.2.3 @@ -879,6 +880,11 @@ dao-api ${project.version} + + org.thingsboard.common + cluster-api + ${project.version} + org.thingsboard.rule-engine rule-engine-api @@ -1382,6 +1388,12 @@ io.grpc grpc-netty ${grpc.version} + + + io.netty + * + + io.grpc @@ -1437,6 +1449,12 @@ ${junit.version} test + + org.awaitility + awaitility + ${awaitility.version} + test + org.hamcrest hamcrest @@ -1580,6 +1598,12 @@ com.microsoft.azure azure-servicebus ${azure-servicebus.version} + + + io.netty + * + + org.passay diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index d32552f63d..30dc57eeb7 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -337,7 +337,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { addTimePageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + urlSecondPart + getTimeUrlParams(pageLink), + baseURL + urlSecondPart + "&" + getTimeUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -1811,12 +1811,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { - restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); + restTemplate.postForLocation(baseURL + "/api/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); } public JsonNode handleTwoWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { return restTemplate.exchange( - baseURL + "/api/plugins/rpc/twoway/{deviceId}", + baseURL + "/api/rpc/twoway/{deviceId}", HttpMethod.POST, new HttpEntity<>(requestBody), new ParameterizedTypeReference() { @@ -2034,7 +2034,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params.put("useStrictDataTypes", Boolean.toString(useStrictDataTypes)); StringBuilder urlBuilder = new StringBuilder(baseURL); - urlBuilder.append("/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&agg={agg}&useStrictDataTypes={useStrictDataTypes}&orderBy={orderBy}"); + urlBuilder.append("/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&limit={limit}&agg={agg}&useStrictDataTypes={useStrictDataTypes}&orderBy={orderBy}"); if (startTime != null) { urlBuilder.append("&startTs={startTs}"); diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index bb348793c1..e0db697309 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -48,6 +48,11 @@ dao-api provided + + org.thingsboard.common + cluster-api + provided + org.thingsboard.common util diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java index 1d74040b34..afdee27b74 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java @@ -40,5 +40,6 @@ public final class RuleEngineDeviceRpcRequest { private final String body; private final long expirationTime; private final boolean restApiCall; + private final String additionalInfo; } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java index 9c43696650..c55e2ccbcf 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.api; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.rpc.RpcError; import java.util.Optional; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 62ed9b0fa0..76b079c3ee 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.api; import io.netty.channel.EventLoopGroup; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -188,6 +189,8 @@ public interface TbContext { DeviceService getDeviceService(); + TbClusterService getClusterService(); + DashboardService getDashboardService(); RuleEngineAlarmService getAlarmService(); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java index a00b2372a2..dfbf72997b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import java.util.List; import java.util.Set; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java index 4b039dbf53..17413c2b0f 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceCredentialsUpdateNotificationMsg.java @@ -16,16 +16,11 @@ package org.thingsboard.rule.engine.api.msg; import lombok.Data; -import lombok.Getter; -import lombok.ToString; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.msg.MsgType; - -import java.util.Set; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; /** * @author Andrew Shvayka diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceEdgeUpdateMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceEdgeUpdateMsg.java index 3c7fc7a68c..00a69091b2 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceEdgeUpdateMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceEdgeUpdateMsg.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; @Data @AllArgsConstructor diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java index 0418986998..47ffcc59ba 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceNameOrTypeUpdateMsg.java @@ -20,6 +20,7 @@ import lombok.Data; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; @Data @AllArgsConstructor diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java index 861e2a3f0d..33c637f9d9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java @@ -191,6 +191,7 @@ public abstract class TbAbstractRelationActionNode log.trace("Pushed Device Created message: {}", savedDevice), throwable -> log.warn("Failed to push Device Created message: {}", savedDevice, throwable)); @@ -259,10 +260,10 @@ public abstract class TbAbstractRelationActionNode saveFuture = ctx.getEdgeEventService().saveAsync(edgeEvent); - Futures.addCallback(saveFuture, new FutureCallback() { - @Override - public void onSuccess(@Nullable EdgeEvent event) { - ctx.tellNext(msg, SUCCESS); - ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId); - } - - @Override - public void onFailure(Throwable th) { - log.warn("[{}] Can't save edge event [{}] for edge [{}]", ctx.getTenantId().getId(), edgeEvent, edgeId.getId(), th); - ctx.tellFailure(msg, th); - } - }, ctx.getDbCallbackExecutor()); + ctx.getEdgeEventService().save(edgeEvent); + ctx.tellNext(msg, SUCCESS); + ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId); } private EdgeEvent buildEdgeEvent(TbMsg msg, TbContext ctx) { @@ -182,13 +171,13 @@ public class TbMsgPushToEdgeNode implements TbNode { case ATTRIBUTES_UPDATED: case POST_ATTRIBUTES: entityBody.put("kv", dataJson); - entityBody.put(SCOPE, config.getScope()); + entityBody.put(SCOPE, getScope(metadata)); break; case ATTRIBUTES_DELETED: List keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() { }); entityBody.put("keys", keys); - entityBody.put(SCOPE, config.getScope()); + entityBody.put(SCOPE, getScope(metadata)); break; case TIMESERIES_UPDATED: entityBody.put("data", dataJson); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 85c2a156b3..cda3269791 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; type = ComponentType.FILTER, name = "message type switch", configClazz = EmptyNodeConfiguration.class, - relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Failed", + relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Failed", "RPC Deleted", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant", "Timeseries Updated", "Timeseries Deleted"}, @@ -105,6 +105,8 @@ public class TbMsgTypeSwitchNode implements TbNode { relationType = "RPC Timeout"; } else if (msg.getType().equals(DataConstants.RPC_FAILED)) { relationType = "RPC Failed"; + } else if (msg.getType().equals(DataConstants.RPC_DELETED)) { + relationType = "RPC Deleted"; } else { relationType = "Other"; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index b0730a5841..2bf9122934 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -188,7 +188,7 @@ class AlarmState { setAlarmConditionMetadata(ruleState, metaData); TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : ServiceQueue.MAIN, "ALARM", originator, msg != null ? msg.getCustomerId() : null, metaData, data); - ctx.tellNext(newMsg, relationType); + ctx.enqueueForTellNext(newMsg, relationType); } protected void setAlarmConditionMetadata(AlarmRuleState ruleState, TbMsgMetaData metaData) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 3d66c569d8..a6d0a23ee0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -100,6 +100,8 @@ public class TbSendRPCRequestNode implements TbNode { params = gson.toJson(paramsEl); } + String additionalInfo = gson.toJson(json.get(DataConstants.ADDITIONAL_INFO)); + RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder() .oneway(oneway) .method(json.get("method").getAsString()) @@ -112,15 +114,16 @@ public class TbSendRPCRequestNode implements TbNode { .expirationTime(expirationTime) .restApiCall(restApiCall) .persisted(persisted) + .additionalInfo(additionalInfo) .build(); ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> { - if (!ruleEngineDeviceRpcResponse.getError().isPresent()) { + if (ruleEngineDeviceRpcResponse.getError().isEmpty()) { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse("{}")); ctx.enqueueForTellNext(next, TbRelationTypes.SUCCESS); } else { TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); - ctx.tellFailure(next, new RuntimeException(ruleEngineDeviceRpcResponse.getError().get().name())); + ctx.enqueueForTellFailure(next, ruleEngineDeviceRpcResponse.getError().get().name()); } }); ctx.ack(msg); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index c3bbc8bcf6..b2c79102c4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -196,7 +196,7 @@ public class TbDeviceProfileNodeTest { TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); TbMsg theMsg2 = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "2"); @@ -207,7 +207,7 @@ public class TbDeviceProfileNodeTest { TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); - verify(ctx).tellNext(theMsg2, "Alarm Updated"); + verify(ctx).enqueueForTellNext(theMsg2, "Alarm Updated"); } @@ -289,7 +289,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -376,7 +376,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -445,7 +445,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -554,7 +554,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -678,7 +678,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -781,7 +781,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -897,7 +897,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -999,7 +999,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg2); verify(ctx).tellSuccess(msg2); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -1082,7 +1082,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -1163,7 +1163,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -1237,7 +1237,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -1321,7 +1321,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } @@ -1407,7 +1407,7 @@ public class TbDeviceProfileNodeTest { node.onMsg(ctx, msg); verify(ctx).tellSuccess(msg); - verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx).enqueueForTellNext(theMsg, "Alarm Created"); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index deea1934db..c53dca9e3f 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -131,6 +131,8 @@ queue: retries: "${TB_KAFKA_RETRIES:1}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" @@ -139,7 +141,11 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - other: + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index b143f2fcd6..b34608d751 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -103,6 +103,8 @@ queue: retries: "${TB_KAFKA_RETRIES:1}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" @@ -111,7 +113,11 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - other: + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 935c8fa461..5be80bd73e 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -156,6 +156,8 @@ queue: retries: "${TB_KAFKA_RETRIES:1}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" @@ -164,7 +166,11 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - other: + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index c678d5afed..82f59f0ce3 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -89,6 +89,7 @@ transport: bind_address: "${MQTT_BIND_ADDRESS:0.0.0.0}" bind_port: "${MQTT_BIND_PORT:1883}" timeout: "${MQTT_TIMEOUT:10000}" + msg_queue_size_per_device_limit: "${MQTT_MSG_QUEUE_SIZE_PER_DEVICE_LIMIT:100}" # messages await in the queue before device connected state. This limit works on low level before TenantProfileLimits mechanism netty: leak_detector_level: "${NETTY_LEAK_DETECTOR_LVL:DISABLED}" boss_group_thread_count: "${NETTY_BOSS_GROUP_THREADS:1}" @@ -136,6 +137,8 @@ queue: retries: "${TB_KAFKA_RETRIES:1}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" @@ -144,7 +147,11 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - other: + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index a7f1879947..7de8b4021b 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -68,6 +68,8 @@ queue: retries: "${TB_KAFKA_RETRIES:1}" batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" + max.request.size: "${TB_KAFKA_MAX_REQUEST_SIZE:1048576}" + max.in.flight.requests.per.connection: "${TB_KAFKA_MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION:5}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" @@ -76,7 +78,11 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - other: + other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside + - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms + value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) + - key: "session.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/consumer-configs.html#consumerconfigs_session.timeout.ms + value: "${TB_QUEUE_KAFKA_SESSION_TIMEOUT_MS:10000}" # (10 seconds) topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index d473296a4c..75c43caa95 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -69,8 +69,11 @@ export interface WidgetSubscriptionApi { } export interface RpcApi { - sendOneWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string) => Observable; - sendTwoWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string) => Observable; + sendOneWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, + persistentPollingInterval?: number, requestUUID?: string) => Observable; + sendTwoWayCommand: (method: string, params?: any, timeout?: number, persistent?: boolean, + persistentPollingInterval?: number, requestUUID?: string) => Observable; + completedCommand: () => void; } export interface IWidgetUtils { @@ -84,6 +87,8 @@ export interface WidgetActionsApi { entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) => void; elementClick: ($event: Event) => void; getActiveEntityInfo: () => SubscriptionEntityInfo; + openDashboardStateInSeparateDialog: (targetDashboardStateId: string, params?: StateParams, dialogTitle?: string, + hideDashboardToolbar?: boolean, dialogWidth?: number, dialogHeight?: number) => void; } export interface AliasInfo { @@ -299,8 +304,10 @@ export interface IWidgetSubscription { onResetTimewindow(): void; updateTimewindowConfig(newTimewindow: Timewindow): void; - sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable; - sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable; + sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, + persistentPollingInterval?: number, requestUUID?: string): Observable; + sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, + persistentPollingInterval?: number, requestUUID?: string): Observable; clearRpcError(): void; subscribe(): void; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 32f0f90005..e34fccf9c8 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -49,7 +49,7 @@ import { toHistoryTimewindow, WidgetTimewindow } from '@app/shared/models/time/time.models'; -import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; +import { forkJoin, Observable, of, ReplaySubject, Subject, throwError, timer } from 'rxjs'; import { CancelAnimationFrame } from '@core/services/raf.service'; import { EntityType } from '@shared/models/entity-type.models'; import { createLabelFromDatasource, deepClone, isDefined, isDefinedAndNotNull, isEqual } from '@core/utils'; @@ -67,8 +67,9 @@ import { KeyFilter, updateDatasourceFromEntityInfo } from '@shared/models/query/query.models'; -import { map } from 'rxjs/operators'; +import { filter, map, switchMap, takeUntil } from 'rxjs/operators'; import { AlarmDataListener } from '@core/api/alarm-data.service'; +import { RpcStatus } from '@shared/models/rpc.models'; const moment = moment_; @@ -644,12 +645,14 @@ export class WidgetSubscription implements IWidgetSubscription { } } - sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable { - return this.sendCommand(true, method, params, timeout, persistent, requestUUID); + sendOneWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, + persistentPollingInterval?: number, requestUUID?: string): Observable { + return this.sendCommand(true, method, params, timeout, persistent, persistentPollingInterval, requestUUID); } - sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, requestUUID?: string): Observable { - return this.sendCommand(false, method, params, timeout, persistent, requestUUID); + sendTwoWayCommand(method: string, params?: any, timeout?: number, persistent?: boolean, + persistentPollingInterval?: number, requestUUID?: string): Observable { + return this.sendCommand(false, method, params, timeout, persistent, persistentPollingInterval, requestUUID); } clearRpcError(): void { @@ -658,12 +661,19 @@ export class WidgetSubscription implements IWidgetSubscription { this.callbacks.onRpcErrorCleared(this); } - sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any, - timeout?: number, persistent?: boolean, requestUUID?: string): Observable { + completedCommand(): void { + this.executingSubjects.forEach(subject => { + subject.next(); + subject.complete(); + }); + } + + sendCommand(oneWayElseTwoWay: boolean, method: string, params?: any, timeout?: number, + persistent?: boolean, persistentPollingInterval?: number, requestUUID?: string): Observable { if (!this.rpcEnabled) { return throwError(new Error('Rpc disabled!')); } else { - if (this.rpcRejection && this.rpcRejection.status !== 408) { + if (this.rpcRejection && this.rpcRejection.status !== 504) { this.rpcRejection = null; this.rpcErrorText = null; this.callbacks.onRpcErrorCleared(this); @@ -677,7 +687,7 @@ export class WidgetSubscription implements IWidgetSubscription { if (timeout && timeout > 0) { requestBody.timeout = timeout; } - const rpcSubject: Subject = new ReplaySubject(); + const rpcSubject: Subject = new Subject(); this.executingRpcRequest = true; this.callbacks.rpcStateChanged(this); if (this.ctx.utils.widgetEditMode) { @@ -695,7 +705,28 @@ export class WidgetSubscription implements IWidgetSubscription { } else { this.executingSubjects.push(rpcSubject); (oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand(this.targetDeviceId, requestBody) : - this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody)) + this.ctx.deviceService.sendTwoWayRpcCommand(this.targetDeviceId, requestBody)).pipe( + switchMap((response) => { + if (persistent && persistentPollingInterval > 0) { + return timer(persistentPollingInterval / 2, persistentPollingInterval).pipe( + switchMap(() => this.ctx.deviceService.getPersistedRpc(response.rpcId, true)), + filter(persistentRespons => + persistentRespons.status !== RpcStatus.DELIVERED && persistentRespons.status !== RpcStatus.QUEUED), + switchMap(persistentResponse => { + if (persistentResponse.status === RpcStatus.TIMEOUT) { + return throwError({status: 504}); + } else if (persistentResponse.status === RpcStatus.FAILED) { + return throwError({status: 502, statusText: persistentResponse.response.error}); + } else { + return of(persistentResponse.response); + } + }), + takeUntil(rpcSubject) + ); + } + return of(response); + }) + ) .subscribe((responseBody) => { this.rpcRejection = null; this.rpcErrorText = null; @@ -715,12 +746,10 @@ export class WidgetSubscription implements IWidgetSubscription { } this.executingRpcRequest = this.executingSubjects.length > 0; this.callbacks.rpcStateChanged(this); - if (!this.executingRpcRequest || rejection.status === 408) { + if (!this.executingRpcRequest || rejection.status === 504) { this.rpcRejection = rejection; - if (rejection.status === 408) { + if (rejection.status === 504) { this.rpcErrorText = 'Request Timeout.'; - } else if (rejection.status === 409) { - this.rpcErrorText = 'Device is offline.'; } else { this.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText; const error = this.extractRejectionErrorText(rejection); diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 1bed864ab1..df7941cef3 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -50,11 +50,17 @@ export class AttributeService { } public deleteEntityTimeseries(entityId: EntityId, timeseries: Array, deleteAllDataForKeys = false, - config?: RequestConfig): Observable { + startTs?: number, endTs?: number, config?: RequestConfig): Observable { const keys = timeseries.map(attribute => encodeURI(attribute.key)).join(','); - return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + - `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}`, - defaultHttpOptionsFromConfig(config)); + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + + `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}`; + if (isDefinedAndNotNull(startTs)) { + url += `&startTs=${startTs}`; + } + if (isDefinedAndNotNull(endTs)) { + url += `&endTs=${endTs}`; + } + return this.http.delete(url, defaultHttpOptionsFromConfig(config)); } public saveEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, @@ -97,7 +103,7 @@ export class AttributeService { }); let deleteEntityTimeseriesObservable: Observable; if (deleteTimeseries.length) { - deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, config); + deleteEntityTimeseriesObservable = this.deleteEntityTimeseries(entityId, deleteTimeseries, true, null, null, config); } else { deleteEntityTimeseriesObservable = of(null); } diff --git a/ui-ngx/src/app/core/http/device.service.ts b/ui-ngx/src/app/core/http/device.service.ts index 21bd04b39d..c8ea7e65ff 100644 --- a/ui-ngx/src/app/core/http/device.service.ts +++ b/ui-ngx/src/app/core/http/device.service.ts @@ -31,6 +31,7 @@ import { import { EntitySubtype } from '@app/shared/models/entity-type.models'; import { AuthService } from '@core/auth/auth.service'; import { BulkImportRequest, BulkImportResult } from '@home/components/import-export/import-export.models'; +import { PersistentRpc } from '@shared/models/rpc.models'; @Injectable({ providedIn: 'root' @@ -131,11 +132,15 @@ export class DeviceService { } public sendOneWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable { - return this.http.post(`/api/plugins/rpc/oneway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); + return this.http.post(`/api/rpc/oneway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); } public sendTwoWayRpcCommand(deviceId: string, requestBody: any, config?: RequestConfig): Observable { - return this.http.post(`/api/plugins/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); + return this.http.post(`/api/rpc/twoway/${deviceId}`, requestBody, defaultHttpOptionsFromConfig(config)); + } + + public getPersistedRpc(rpcId: string, fullResponse = false, config?: RequestConfig): Observable { + return this.http.get(`/api/rpc/persistent/${rpcId}`, defaultHttpOptionsFromConfig(config)); } public findByQuery(query: DeviceSearchQuery, diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 5449cac3e1..be18613587 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -47,7 +47,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { private internalUrlPrefixes = [ '/api/auth/token', - '/api/plugins/rpc' + '/api/rpc' ]; private activeRequests = 0; @@ -142,7 +142,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { } } else if (errorResponse.status === 0 || errorResponse.status === -1) { this.showError('Unable to connect'); - } else if (!req.url.startsWith('/api/plugins/rpc')) { + } else if (!(req.url.startsWith('/api/rpc') || req.url.startsWith('/api/plugins/rpc'))) { if (errorResponse.status === 404) { if (!ignoreErrors) { this.showError(req.method + ': ' + req.url + '
' + diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts index 63ad7dbcaf..5acf57095f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/state-controller.component.ts @@ -129,7 +129,8 @@ export abstract class StateControllerComponent implements IStateControllerCompon protected updateStateParam(newState: string, replaceCurrentHistoryUrl = false) { this.currentState = newState; if (this.syncStateWithQueryParam) { - const queryParams: Params = {state: encodeURIComponent(this.currentState)}; + const state = this.currentState ? encodeURIComponent(this.currentState) : this.currentState; + const queryParams: Params = {state}; this.ngZone.run(() => { this.router.navigate( [], diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html index c41b4b6900..9bc5c799ac 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.html @@ -60,10 +60,9 @@ {{ 'device.lwm2m-security-config.client-key-pattern' | translate }} - + {{ 'device.lwm2m-security-config.client-key-length' | translate: { - count: lenMaxKeyClient + count: allowLengthKey.join(', ') } }} diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts index a118de24d6..99123a5db3 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-lwm2m.component.ts @@ -16,6 +16,7 @@ import { Component, forwardRef, OnDestroy } from '@angular/core'; import { + AbstractControl, ControlValueAccessor, FormBuilder, FormGroup, @@ -64,6 +65,7 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType); credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap; lenMaxKeyClient = LEN_MAX_PSK; + allowLengthKey: number[]; private destroy$ = new Subject(); private propagateChange = (v: any) => {}; @@ -119,7 +121,7 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va config.key = this.lwm2mConfigFormGroup.get('client.key').value; break; } - this.lwm2mConfigFormGroup.get('client').patchValue(config, {emitEvent: false}); + this.lwm2mConfigFormGroup.get('client').reset(config, {emitEvent: false}); this.securityConfigClientUpdateValidators(type); } @@ -135,11 +137,13 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va break; case Lwm2mSecurityType.PSK: this.lenMaxKeyClient = LEN_MAX_PSK; + this.allowLengthKey = [32, 64, LEN_MAX_PSK]; this.setValidatorsPskRpk(mode); this.lwm2mConfigFormGroup.get('client.identity').enable({emitEvent: false}); break; case Lwm2mSecurityType.RPK: this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; + this.allowLengthKey = [LEN_MAX_PUBLIC_KEY_RPK]; this.setValidatorsPskRpk(mode); this.lwm2mConfigFormGroup.get('client.identity').disable({emitEvent: false}); break; @@ -164,13 +168,22 @@ export class DeviceCredentialsLwm2mComponent implements ControlValueAccessor, Va this.lwm2mConfigFormGroup.get('client.key').setValidators([ Validators.required, Validators.pattern(KEY_REGEXP_HEX_DEC), - Validators.maxLength(this.lenMaxKeyClient), - Validators.minLength(this.lenMaxKeyClient) + this.maxLength(this.allowLengthKey) ]); this.lwm2mConfigFormGroup.get('client.key').enable({emitEvent: false}); this.lwm2mConfigFormGroup.get('client.cert').disable({emitEvent: false}); } + private maxLength(keyLengths: number[]) { + return (control: AbstractControl): ValidationErrors | null => { + const value = control.value; + if (keyLengths.some(len => value.length === len)) { + return null; + } + return {length: true}; + }; + } + private initLwm2mConfigForm = (): FormGroup => { const formGroup = this.fb.group({ client: this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html index 0b798e6dbf..126177ba11 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials-mqtt-basic.component.html @@ -35,12 +35,8 @@ - + type="password"> + - - {{ 'device-profile.edrx-cycle' | translate }} - - - {{ 'device-profile.edrx-cycle-required' | translate }} - - - {{ 'device-profile.edrx-cycle-pattern' | translate }} - - - - {{ 'device-profile.paging-transmission-window' | translate }} - - - {{ 'device-profile.paging-transmission-window-required' | translate }} - - - {{ 'device-profile.paging-transmission-window-pattern' | translate }} - - + + + + - - {{ 'device-profile.psm-activity-timer' | translate }} - - - {{ 'device-profile.psm-activity-timer-required' | translate }} - - - {{ 'device-profile.psm-activity-timer-pattern' | translate }} - - + + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.ts index cae224ad2e..366fa9a9ca 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/common/power-mode-setting.component.ts @@ -16,7 +16,12 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { PowerMode, PowerModeTranslationMap } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; +import { + DEFAULT_EDRX_CYCLE, + DEFAULT_PAGING_TRANSMISSION_WINDOW, DEFAULT_PSM_ACTIVITY_TIMER, + PowerMode, + PowerModeTranslationMap +} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; @@ -63,13 +68,13 @@ export class PowerModeSettingComponent implements OnInit, OnDestroy { private disablePSKMode() { this.parentForm.get('psmActivityTimer').disable({emitEvent: false}); - this.parentForm.get('psmActivityTimer').reset(0, {emitEvent: false}); + this.parentForm.get('psmActivityTimer').reset(DEFAULT_PSM_ACTIVITY_TIMER, {emitEvent: false}); } private disableEdrxMode() { this.parentForm.get('edrxCycle').disable({emitEvent: false}); - this.parentForm.get('edrxCycle').reset(0, {emitEvent: false}); + this.parentForm.get('edrxCycle').reset(DEFAULT_EDRX_CYCLE, {emitEvent: false}); this.parentForm.get('pagingTransmissionWindow').disable({emitEvent: false}); - this.parentForm.get('pagingTransmissionWindow').reset(0, {emitEvent: false}); + this.parentForm.get('pagingTransmissionWindow').reset(DEFAULT_PAGING_TRANSMISSION_WINDOW, {emitEvent: false}); } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.html b/ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.html new file mode 100644 index 0000000000..1a71676bf8 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.html @@ -0,0 +1,40 @@ + +
+ + {{ labelText | translate }} + + + {{ requiredText | translate }} + + + {{ patternText | translate }} + + + {{ (minText || patternText) | translate : {min: minTime/1000} }} + + + + device-profile.condition-duration-time-unit + + + {{ timeUnitTranslations.get(timeUnit) | translate }} + + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.ts new file mode 100644 index 0000000000..b868d8c49e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/common/time-unit-select.component.ts @@ -0,0 +1,194 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { + FullTimeUnit, + HOUR, + MINUTE, + SECOND, + TimeUnit, + TimeUnitMilli, + timeUnitTranslationMap +} from '@shared/models/time/time.models'; +import { isDefinedAndNotNull, isNumber } from '@core/utils'; + +interface FormGroupModel { + time: number; + unit: FullTimeUnit; +} + +@Component({ + selector: 'tb-time-unit-select', + templateUrl: './time-unit-select.component.html', + styleUrls: [], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TimeUnitSelectComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => TimeUnitSelectComponent), + multi: true + } + ] +}) +export class TimeUnitSelectComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { + + timeUnitSelectFormGroup: FormGroup; + + timeUnits = Object.values({...TimeUnitMilli, ...TimeUnit}).filter(item => item !== TimeUnit.DAYS); + timeUnitTranslations = timeUnitTranslationMap; + + private destroy$ = new Subject(); + + private timeUnitToTimeMap = new Map( + [ + [TimeUnitMilli.MILLISECONDS, 1], + [TimeUnit.SECONDS, SECOND], + [TimeUnit.MINUTES, MINUTE], + [TimeUnit.HOURS, HOUR] + ] + ); + + private timeToTimeUnitMap = new Map( + [ + [SECOND, TimeUnitMilli.MILLISECONDS], + [MINUTE, TimeUnit.SECONDS], + [HOUR, TimeUnit.MINUTES] + ] + ); + + @Input() + disabled: boolean; + + @Input() + labelText: string; + + @Input() + requiredText: string; + + @Input() + patternText: string; + + @Input() + minTime = 0; + + @Input() + minText: string; + + private propagateChange = (v: any) => { + } + + constructor(private fb: FormBuilder) { + } + + ngOnInit() { + this.timeUnitSelectFormGroup = this.fb.group({ + time: [0, [Validators.required, Validators.min(this.minTime), Validators.pattern('[0-9]*')]], + unit: [TimeUnitMilli.MILLISECONDS] + }); + this.timeUnitSelectFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.updateModel(value); + }); + this.timeUnitSelectFormGroup.get('unit').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((unit: FullTimeUnit) => { + if (this.minTime > 0) { + const unitTime = this.timeUnitToTimeMap.get(unit); + const validationTime = Math.ceil(this.minTime / unitTime); + this.timeUnitSelectFormGroup.get('time').setValidators([Validators.required, Validators.min(validationTime), Validators.pattern('[0-9]*')]); + this.timeUnitSelectFormGroup.get('time').updateValueAndValidity({emitEvent: false}); + } + }); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + registerOnChange(fn: any) { + this.propagateChange = fn; + } + + registerOnTouched(fn: any) { + } + + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + if (this.disabled) { + this.timeUnitSelectFormGroup.disable({emitEvent: false}); + } else { + this.timeUnitSelectFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: number) { + const formValue: FormGroupModel = { + time: 0, + unit: TimeUnitMilli.MILLISECONDS + }; + if (isDefinedAndNotNull(value) && isNumber(value) && value >= 0) { + formValue.unit = this.calculateTimeUnit(value); + formValue.time = value / this.timeUnitToTimeMap.get(formValue.unit); + } + this.timeUnitSelectFormGroup.reset(formValue, {emitEvent: false}); + this.timeUnitSelectFormGroup.get('unit').updateValueAndValidity({onlySelf: true}); + } + + validate(): ValidationErrors | null { + return this.timeUnitSelectFormGroup.valid ? null : { + timeUnitSelect: false + }; + } + + private updateModel(value: FormGroupModel) { + const time = value.time * this.timeUnitToTimeMap.get(value.unit); + this.propagateChange(time); + } + + private calculateTimeUnit(value: number): FullTimeUnit { + if (value === 0) { + return TimeUnitMilli.MILLISECONDS; + } + const iterators = this.timeToTimeUnitMap[Symbol.iterator](); + let iterator = iterators.next(); + while (!iterator.done) { + if (!Number.isInteger(value / iterator.value[0])) { + return iterator.value[1]; + } + iterator = iterators.next(); + } + return TimeUnit.HOURS; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index 7dfa93da5a..2495a16eff 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -18,7 +18,8 @@ import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, - FormGroup, NG_VALIDATORS, + FormGroup, + NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator, @@ -30,11 +31,14 @@ import { BingingMode, BingingModeTranslationsMap, DEFAULT_BINDING, + DEFAULT_EDRX_CYCLE, DEFAULT_FW_UPDATE_RESOURCE, DEFAULT_ID_SERVER, DEFAULT_LIFE_TIME, DEFAULT_MIN_PERIOD, DEFAULT_NOTIF_IF_DESIBLED, + DEFAULT_PAGING_TRANSMISSION_WINDOW, + DEFAULT_PSM_ACTIVITY_TIMER, DEFAULT_SW_UPDATE_RESOURCE, getDefaultBootstrapServerSecurityConfig, getDefaultLwM2MServerSecurityConfig, @@ -120,9 +124,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro fwUpdateResource: [{value: '', disabled: true}, []], swUpdateResource: [{value: '', disabled: true}, []], powerMode: [PowerMode.DRX, Validators.required], - edrxCycle: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - psmActivityTimer: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - pagingTransmissionWindow: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + edrxCycle: [{disabled: true, value: 0}, Validators.required], + psmActivityTimer: [{disabled: true, value: 0}, Validators.required], + pagingTransmissionWindow: [{disabled: true, value: 0}, Validators.required], compositeOperationsSupport: [false] }) }); @@ -247,9 +251,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro fwUpdateResource: this.configurationValue.clientLwM2mSettings.fwUpdateResource || '', swUpdateResource: this.configurationValue.clientLwM2mSettings.swUpdateResource || '', powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX, - edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || 0, - pagingTransmissionWindow: this.configurationValue.clientLwM2mSettings.pagingTransmissionWindow || 0, - psmActivityTimer: this.configurationValue.clientLwM2mSettings.psmActivityTimer || 0, + edrxCycle: this.configurationValue.clientLwM2mSettings.edrxCycle || DEFAULT_EDRX_CYCLE, + pagingTransmissionWindow: + this.configurationValue.clientLwM2mSettings.pagingTransmissionWindow || DEFAULT_PAGING_TRANSMISSION_WINDOW, + psmActivityTimer: this.configurationValue.clientLwM2mSettings.psmActivityTimer || DEFAULT_PSM_ACTIVITY_TIMER, compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false } }, diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index 2eb83a49c0..5e31251786 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -42,7 +42,9 @@ export const INSTANCES_ID_VALUE_MAX = 65535; export const DEFAULT_OTA_UPDATE_PROTOCOL = 'coap://'; export const DEFAULT_FW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC; export const DEFAULT_SW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC; - +export const DEFAULT_PSM_ACTIVITY_TIMER = 10000; +export const DEFAULT_EDRX_CYCLE = 81000; +export const DEFAULT_PAGING_TRANSMISSION_WINDOW = 10000; export enum BingingMode { U = 'U', diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts index afdb3eafc1..f8685cc193 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, Input, Optional } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, Input, Optional } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -43,8 +43,9 @@ export class TenantProfileComponent extends EntityComponent { protected translate: TranslateService, @Optional() @Inject('entity') protected entityValue: TenantProfile, @Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - protected fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + protected fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } hideDelete() { diff --git a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html index 5f675e43f2..8137affa27 100644 --- a/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/sms/aws-sns-provider-configuration.component.html @@ -26,6 +26,7 @@ admin.aws-secret-access-key + {{ 'admin.aws-secret-access-key-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html index 3af73e8932..d636ddffc0 100644 --- a/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/sms/twilio-sms-provider-configuration.component.html @@ -37,6 +37,7 @@ admin.twilio-account-token + {{ 'admin.twilio-account-token-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts index 847037efe4..547f91b7bb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/knob.component.ts @@ -36,6 +36,8 @@ interface KnobSettings { getValueMethod: string; setValueMethod: string; requestTimeout: number; + requestPersistent: boolean; + persistentPollingInterval: number; } @Component({ @@ -80,6 +82,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { private isSimulated: boolean; private requestTimeout: number; + private requestPersistent: boolean; + private persistentPollingInterval: number; private getValueMethod: string; private setValueMethod: string; @@ -138,6 +142,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { if (this.knobResize$) { this.knobResize$.disconnect(); } + this.ctx.controlApi.completedCommand(); } private init() { @@ -160,8 +165,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { maxValue: this.maxValue, gaugeType: 'donut', dashThickness: 2, - donutStartAngle: 3/4*Math.PI, - donutEndAngle: 9/4*Math.PI, + donutStartAngle: 3 / 4 * Math.PI, + donutEndAngle: 9 / 4 * Math.PI, animation: false }; @@ -209,10 +214,10 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { e.preventDefault(); const offset = this.knob.offset(); const center = { - y : offset.top + this.knob.height()/2, - x: offset.left + this.knob.width()/2 + y: offset.top + this.knob.height() / 2, + x: offset.left + this.knob.width() / 2 }; - const rad2deg = 180/Math.PI; + const rad2deg = 180 / Math.PI; $(document).on('mousemove.rem touchmove.rem', (ev) => { this.moving = true; @@ -220,21 +225,20 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { const a = center.y - t.pageY; const b = center.x - t.pageX; - let deg = Math.atan2(a,b)*rad2deg; - if(deg < 0){ + let deg = Math.atan2(a, b) * rad2deg; + if (deg < 0) { deg = 360 + deg; } - if(this.startDeg === -1){ + if (this.startDeg === -1) { this.startDeg = deg; } - let tmp = Math.floor((deg-this.startDeg) + this.rotation); + let tmp = Math.floor((deg - this.startDeg) + this.rotation); - if(tmp < 0){ + if (tmp < 0) { tmp = 360 + tmp; - } - else if(tmp > 359){ + } else if (tmp > 359) { tmp = tmp % 360; } @@ -251,7 +255,7 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { } } } - if(Math.abs(tmp - this.lastDeg) > 180){ + if (Math.abs(tmp - this.lastDeg) > 180) { this.startDeg = deg; this.rotation = this.currentDeg; return false; @@ -260,12 +264,12 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { this.currentDeg = tmp; this.lastDeg = tmp; - this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)'); + this.knobTopPointerContainer.css('transform', 'rotate(' + (this.currentDeg) + 'deg)'); this.turn(this.degreeToRatio(this.currentDeg)); }); - $(document).on('mouseup.rem touchend.rem',() => { - if(this.newValue !== this.rpcValue && this.moving) { + $(document).on('mouseup.rem touchend.rem', () => { + if (this.newValue !== this.rpcValue && this.moving) { this.rpcUpdateValue(this.newValue); } this.knob.off('.rem'); @@ -285,6 +289,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { if (settings.requestTimeout) { this.requestTimeout = settings.requestTimeout; } + this.requestPersistent = false; + if (settings.requestPersistent) { + this.requestPersistent = settings.requestPersistent; + } + this.persistentPollingInterval = 5000; + if (settings.persistentPollingInterval) { + this.persistentPollingInterval = settings.persistentPollingInterval; + } this.getValueMethod = 'getValue'; if (settings.getValueMethod && settings.getValueMethod.length) { this.getValueMethod = settings.getValueMethod; @@ -312,15 +324,15 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { } private degreeToRatio(degree: number): number { - return (degree-this.minDeg)/(this.maxDeg-this.minDeg); + return (degree - this.minDeg) / (this.maxDeg - this.minDeg); } private ratioToDegree(ratio: number): number { - return this.minDeg + ratio*(this.maxDeg-this.minDeg); + return this.minDeg + ratio * (this.maxDeg - this.minDeg); } private turn(ratio: number) { - this.newValue = Number((this.minValue + (this.maxValue - this.minValue)*ratio).toFixed(this.ctx.decimals)); + this.newValue = Number((this.minValue + (this.maxValue - this.minValue) * ratio).toFixed(this.ctx.decimals)); if (this.canvasBar.value !== this.newValue) { this.canvasBar.value = this.newValue; } @@ -339,14 +351,14 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { this.setFontSize(this.knobTitle, this.title, this.knobTitleContainer.height(), this.knobTitleContainer.width()); this.setFontSize(this.knobError, this.error, this.knobErrorContainer.height(), this.knobErrorContainer.width()); const minmaxHeight = this.knobMinmaxContainer.height(); - this.minmaxLabel.css({fontSize: minmaxHeight+'px', lineHeight: minmaxHeight+'px'}); + this.minmaxLabel.css({fontSize: minmaxHeight + 'px', lineHeight: minmaxHeight + 'px'}); this.checkValueSize(); } private checkValueSize() { - const fontSize = this.knobValueContainer.height()/3.3; + const fontSize = this.knobValueContainer.height() / 3.3; const containerWidth = this.knobValueContainer.width(); - this.setFontSize(this.knobValue, this.value+'', fontSize, containerWidth); + this.setFontSize(this.knobValue, this.value + '', fontSize, containerWidth); } private setFontSize(element: JQuery, text: string, fontSize: number, maxWidth: number) { @@ -358,19 +370,19 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { } textWidth = this.measureTextWidth(text, fontSize); } - element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'}); } private measureTextWidth(text: string, fontSize: number): number { - this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'}); this.textMeasure.html(text); return this.textMeasure.width(); } private setValue(value: number) { - const ratio = (value-this.minValue) / (this.maxValue - this.minValue); + const ratio = (value - this.minValue) / (this.maxValue - this.minValue); this.rotation = this.lastDeg = this.currentDeg = this.ratioToDegree(ratio); - this.knobTopPointerContainer.css('transform','rotate('+(this.currentDeg)+'deg)'); + this.knobTopPointerContainer.css('transform', 'rotate(' + (this.currentDeg) + 'deg)'); if (this.canvasBar.value !== value) { this.canvasBar.value = value; } @@ -402,7 +414,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { private rpcRequestValue() { this.error = ''; - this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout, + this.requestPersistent, this.persistentPollingInterval).subscribe( (responseBody) => { if (isNumber(responseBody)) { const numValue = Number(Number(responseBody).toFixed(this.ctx.decimals)); @@ -429,7 +442,8 @@ export class KnobComponent extends PageComponent implements OnInit, OnDestroy { this.executingUpdateValue = true; } this.error = ''; - this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout).subscribe( + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, value, this.requestTimeout, + this.requestPersistent, this.persistentPollingInterval).subscribe( () => { this.executingUpdateValue = false; if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts index de288d7d5a..92ea5ba8db 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/led-indicator.component.ts @@ -44,6 +44,8 @@ interface LedIndicatorSettings { valueAttribute: string; parseValueFunction: string; requestTimeout: number; + requestPersistent: boolean; + persistentPollingInterval: number; } @Component({ @@ -77,6 +79,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe private isSimulated: boolean; private requestTimeout: number; + private requestPersistent: boolean; + private persistentPollingInterval: number; private retrieveValueMethod: RetrieveValueMethod; private parseValueFunction: (data: any) => boolean; private performCheckStatus: boolean; @@ -141,6 +145,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe if (this.ledResize$) { this.ledResize$.disconnect(); } + this.ctx.controlApi.completedCommand(); } private init() { @@ -168,6 +173,14 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe if (settings.requestTimeout) { this.requestTimeout = settings.requestTimeout; } + this.requestPersistent = false; + if (settings.requestPersistent) { + this.requestPersistent = settings.requestPersistent; + } + this.persistentPollingInterval = 5000; + if (settings.persistentPollingInterval) { + this.persistentPollingInterval = settings.persistentPollingInterval; + } this.retrieveValueMethod = 'attribute'; if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { this.retrieveValueMethod = settings.retrieveValueMethod; @@ -219,11 +232,11 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe fontSize--; textWidth = this.measureTextWidth(text, fontSize); } - element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'}); } private measureTextWidth(text: string, fontSize: number): number { - this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'}); this.textMeasure.text(text); return this.textMeasure.width(); } @@ -259,7 +272,8 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe return; } this.error = ''; - this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout).subscribe( + this.ctx.controlApi.sendTwoWayCommand(this.checkStatusMethod, null, this.requestTimeout, + this.requestPersistent, this.persistentPollingInterval).subscribe( (responseBody) => { const status = !!responseBody; if (status) { @@ -313,7 +327,7 @@ export class LedIndicatorComponent extends PageComponent implements OnInit, OnDe ); } - private onDataUpdated (subscription: IWidgetSubscription, detectChanges: boolean) { + private onDataUpdated(subscription: IWidgetSubscription, detectChanges: boolean) { let value = false; const data = subscription.data; if (data.length) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts index a0e894d711..44c7d8d8b1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/round-switch.component.ts @@ -38,6 +38,8 @@ interface RoundSwitchSettings { parseValueFunction: string; convertValueFunction: string; requestTimeout: number; + requestPersistent: boolean; + persistentPollingInterval: number; } @Component({ @@ -67,6 +69,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes private isSimulated: boolean; private requestTimeout: number; + private requestPersistent: boolean; + private persistentPollingInterval: number; private retrieveValueMethod: RetrieveValueMethod; private valueKey: string; private parseValueFunction: (data: any) => boolean; @@ -125,6 +129,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes if (this.switchResize$) { this.switchResize$.disconnect(); } + this.ctx.controlApi.completedCommand(); } private init() { @@ -143,6 +148,14 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes if (settings.requestTimeout) { this.requestTimeout = settings.requestTimeout; } + this.requestPersistent = false; + if (settings.requestPersistent) { + this.requestPersistent = settings.requestPersistent; + } + this.persistentPollingInterval = 5000; + if (settings.persistentPollingInterval) { + this.persistentPollingInterval = settings.persistentPollingInterval; + } this.retrieveValueMethod = 'rpc'; if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { this.retrieveValueMethod = settings.retrieveValueMethod; @@ -193,7 +206,7 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes const width = this.switchContainer.width(); const height = this.switchContainer.height(); const size = Math.min(width, height); - const scale = size/260; + const scale = size / 260; this.switchElement.css({ '-webkit-transform': `scale(${scale})`, '-moz-transform': `scale(${scale})`, @@ -213,11 +226,11 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes fontSize--; textWidth = this.measureTextWidth(text, fontSize); } - element.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + element.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'}); } private measureTextWidth(text: string, fontSize: number): number { - this.textMeasure.css({fontSize: fontSize+'px', lineHeight: fontSize+'px'}); + this.textMeasure.css({fontSize: fontSize + 'px', lineHeight: fontSize + 'px'}); this.textMeasure.text(text); return this.textMeasure.width(); } @@ -239,7 +252,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes private rpcRequestValue() { this.error = ''; - this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout, + this.requestPersistent, this.persistentPollingInterval).subscribe( (responseBody) => { this.setValue(this.parseValueFunction(responseBody)); }, @@ -260,7 +274,8 @@ export class RoundSwitchComponent extends PageComponent implements OnInit, OnDes this.executingUpdateValue = true; } this.error = ''; - this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe( + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout, + this.requestPersistent, this.persistentPollingInterval).subscribe( () => { this.executingUpdateValue = false; if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts index d2cf53265f..697c837a6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/rpc/switch.component.ts @@ -42,6 +42,8 @@ interface SwitchSettings { parseValueFunction: string; convertValueFunction: string; requestTimeout: number; + requestPersistent: boolean; + persistentPollingInterval: number; } @Component({ @@ -74,6 +76,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy private isSimulated: boolean; private requestTimeout: number; + private requestPersistent: boolean; + private persistentPollingInterval: number; private retrieveValueMethod: RetrieveValueMethod; private valueKey: string; private parseValueFunction: (data: any) => boolean; @@ -133,6 +137,7 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy if (this.switchResize$) { this.switchResize$.disconnect(); } + this.ctx.controlApi.completedCommand(); } private init() { @@ -152,6 +157,14 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy if (settings.requestTimeout) { this.requestTimeout = settings.requestTimeout; } + this.requestPersistent = false; + if (settings.requestPersistent) { + this.requestPersistent = settings.requestPersistent; + } + this.persistentPollingInterval = 5000; + if (settings.persistentPollingInterval) { + this.persistentPollingInterval = settings.persistentPollingInterval; + } this.retrieveValueMethod = 'rpc'; if (settings.retrieveValueMethod && settings.retrieveValueMethod.length) { this.retrieveValueMethod = settings.retrieveValueMethod; @@ -257,7 +270,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy private rpcRequestValue() { this.error = ''; - this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout).subscribe( + this.ctx.controlApi.sendTwoWayCommand(this.getValueMethod, null, this.requestTimeout, + this.requestPersistent, this.persistentPollingInterval).subscribe( (responseBody) => { this.setValue(this.parseValueFunction(responseBody)); this.ctx.detectChanges(); @@ -279,7 +293,8 @@ export class SwitchComponent extends PageComponent implements OnInit, OnDestroy this.executingUpdateValue = true; } this.error = ''; - this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout).subscribe( + this.ctx.controlApi.sendOneWayCommand(this.setValueMethod, this.convertValueFunction(value), this.requestTimeout, + this.requestPersistent, this.persistentPollingInterval).subscribe( () => { this.executingUpdateValue = false; if (this.scheduledValue != null && this.scheduledValue !== this.rpcValue) { diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index dee17a000c..fc7637baef 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -284,7 +284,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI getActionDescriptors: this.getActionDescriptors.bind(this), handleWidgetAction: this.handleWidgetAction.bind(this), elementClick: this.elementClick.bind(this), - getActiveEntityInfo: this.getActiveEntityInfo.bind(this) + getActiveEntityInfo: this.getActiveEntityInfo.bind(this), + openDashboardStateInSeparateDialog: this.openDashboardStateInSeparateDialog.bind(this) }; this.widgetContext.customHeaderActions = []; @@ -1025,7 +1026,8 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.updateEntityParams(params, targetEntityParamName, targetEntityId, entityName, entityLabel); if (type === WidgetActionType.openDashboardState) { if (descriptor.openInSeparateDialog) { - this.openDashboardStateInDialog(descriptor, entityId, entityName, additionalParams, entityLabel); + this.openDashboardStateInSeparateDialog(descriptor.targetDashboardStateId, params, descriptor.dialogTitle, + descriptor.dialogHideDashboardToolbar, descriptor.dialogWidth, descriptor.dialogHeight); } else { this.widgetContext.stateController.openState(targetDashboardStateId, params, descriptor.openRightLayout); } @@ -1276,22 +1278,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } - private openDashboardStateInDialog(descriptor: WidgetActionDescriptor, - entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) { + private openDashboardStateInSeparateDialog(targetDashboardStateId: string, params?: StateParams, dialogTitle?: string, + hideDashboardToolbar = true, dialogWidth?: number, dialogHeight?: number) { const dashboard = deepClone(this.widgetContext.stateController.dashboardCtrl.dashboardCtx.getDashboard()); const stateObject: StateObject = {}; - stateObject.params = {}; - const targetEntityParamName = descriptor.stateEntityParamName; - const targetDashboardStateId = descriptor.targetDashboardStateId; - let targetEntityId: EntityId; - if (descriptor.setEntityId) { - targetEntityId = entityId; - } - this.updateEntityParams(stateObject.params, targetEntityParamName, targetEntityId, entityName, entityLabel); + stateObject.params = params; if (targetDashboardStateId) { stateObject.id = targetDashboardStateId; } - let title = descriptor.dialogTitle; + let title = dialogTitle; if (!title) { if (targetDashboardStateId && dashboard.configuration.states) { const dashboardState = dashboard.configuration.states[targetDashboardStateId]; @@ -1304,7 +1299,6 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI title = dashboard.title; } title = this.utils.customTranslation(title, title); - const params = stateObject.params; const paramsEntityName = params && params.entityName ? params.entityName : ''; const paramsEntityLabel = params && params.entityLabel ? params.entityLabel : ''; title = insertVariable(title, 'entityName', paramsEntityName); @@ -1324,28 +1318,27 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI dashboard, state: objToBase64([ stateObject ]), title, - hideToolbar: descriptor.dialogHideDashboardToolbar, - width: descriptor.dialogWidth, - height: descriptor.dialogHeight + hideToolbar: hideDashboardToolbar, + width: dialogWidth, + height: dialogHeight } }); } private elementClick($event: Event) { - const e = ($event.target || $event.srcElement) as Element; - if (e.id) { - const descriptors = this.getActionDescriptors('elementClick'); - if (descriptors.length) { - descriptors.forEach((descriptor) => { - if (descriptor.name === e.id) { - $event.stopPropagation(); - const entityInfo = this.getActiveEntityInfo(); - const entityId = entityInfo ? entityInfo.entityId : null; - const entityName = entityInfo ? entityInfo.entityName : null; - const entityLabel = entityInfo && entityInfo.entityLabel ? entityInfo.entityLabel : null; - this.handleWidgetAction($event, descriptor, entityId, entityName, null, entityLabel); - } - }); + const elementClicked = ($event.target || $event.srcElement) as Element; + const descriptors = this.getActionDescriptors('elementClick'); + if (descriptors.length) { + const idsList = descriptors.map(descriptor => `#${descriptor.name}`).join(','); + const targetElement = $(elementClicked).closest(idsList, this.widgetContext.$container[0]); + if (targetElement.length && targetElement[0].id) { + $event.stopPropagation(); + const descriptor = descriptors.find(descriptorInfo => descriptorInfo.name === targetElement[0].id); + const entityInfo = this.getActiveEntityInfo(); + const entityId = entityInfo ? entityInfo.entityId : null; + const entityName = entityInfo ? entityInfo.entityName : null; + const entityLabel = entityInfo && entityInfo.entityLabel ? entityInfo.entityLabel : null; + this.handleWidgetAction($event, descriptor, entityId, entityName, null, entityLabel); } } } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 2be39957e8..f93c5d184a 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -202,6 +202,13 @@ export class WidgetContext { } else { return of(null); } + }, + completedCommand: () => { + if (this.defaultSubscription) { + return this.defaultSubscription.completedCommand(); + } else { + return of(null); + } } }; diff --git a/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html index 1809e28b0a..3a40849616 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/mail-server.component.html @@ -39,7 +39,7 @@ admin.smtp-protocol - + {{protocol.toUpperCase()}} @@ -119,6 +119,7 @@ admin.proxy-password + @@ -126,10 +127,14 @@ - + + {{ 'admin.change-password' | translate }} + + common.password +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index bf5dc75023..61e001e75b 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -98,9 +98,9 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha mapperConfigType = MapperConfigType; mapperConfigTypes = Object.keys(MapperConfigType); tenantNameStrategies = Object.keys(TenantNameStrategy); - protocols = Object.keys(DomainSchema); + protocols = Object.values(DomainSchema); domainSchemaTranslations = domainSchemaTranslations; - platformTypes = Object.keys(PlatformType); + platformTypes = Object.values(PlatformType); platformTypeTranslations = platformTypeTranslations; templateProvider = ['Custom']; diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts index 50d565ccc0..a6a14106d5 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Subject } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -30,7 +30,7 @@ import { ResourceTypeTranslationMap } from '@shared/models/resource.models'; import { pairwise, startWith, takeUntil } from 'rxjs/operators'; -import { ActionNotificationShow } from "@core/notification/notification.actions"; +import { ActionNotificationShow } from '@core/notification/notification.actions'; @Component({ selector: 'tb-resources-library', @@ -48,8 +48,9 @@ export class ResourcesLibraryComponent extends EntityComponent impleme protected translate: TranslateService, @Inject('entity') protected entityValue: Resource, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { @@ -102,7 +103,7 @@ export class ResourcesLibraryComponent extends EntityComponent impleme if (this.isAdd) { form.addControl('data', this.fb.control(null, Validators.required)); } - return form + return form; } updateForm(entity: Resource) { diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts index 2ec441c2d3..0ce8f8e6e7 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -41,8 +41,9 @@ export class AssetComponent extends EntityComponent { protected translate: TranslateService, @Inject('entity') protected entityValue: AssetInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts index 20e49d118b..2c6e403767 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -42,8 +42,9 @@ export class CustomerComponent extends ContactBasedComponent { protected translate: TranslateService, @Inject('entity') protected entityValue: Customer, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - protected fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + protected fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } hideDelete() { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts index 13a0340780..35e6daec22 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -49,8 +49,9 @@ export class DashboardFormComponent extends EntityComponent { private dashboardService: DashboardService, @Inject('entity') protected entityValue: Dashboard, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts index 046585e00e..38e14e3fe6 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/data/coap-device-transport-configuration.component.ts @@ -71,9 +71,9 @@ export class CoapDeviceTransportConfigurationComponent implements ControlValueAc ngOnInit() { this.coapDeviceTransportForm = this.fb.group({ powerMode: [null], - edrxCycle: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - psmActivityTimer: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - pagingTransmissionWindow: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]] + edrxCycle: [{disabled: true, value: 0}, Validators.required], + psmActivityTimer: [{disabled: true, value: 0}, Validators.required], + pagingTransmissionWindow: [{disabled: true, value: 0}, Validators.required] }); this.coapDeviceTransportForm.valueChanges.pipe( takeUntil(this.destroy$) diff --git a/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts index 8046dde486..f891166cd0 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/data/lwm2m-device-transport-configuration.component.ts @@ -71,9 +71,9 @@ export class Lwm2mDeviceTransportConfigurationComponent implements ControlValueA ngOnInit() { this.lwm2mDeviceTransportConfigurationFormGroup = this.fb.group({ powerMode: [null], - edrxCycle: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - psmActivityTimer: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], - pagingTransmissionWindow: [{disabled: true, value: 0}, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]] + edrxCycle: [{disabled: true, value: 0}, Validators.required], + psmActivityTimer: [{disabled: true, value: 0}, Validators.required], + pagingTransmissionWindow: [{disabled: true, value: 0}, Validators.required] }); this.lwm2mDeviceTransportConfigurationFormGroup.valueChanges.pipe( takeUntil(this.destroy$) diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index c82ad1c26a..b915d791ab 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -56,8 +56,9 @@ export class DeviceComponent extends EntityComponent { protected translate: TranslateService, @Inject('entity') protected entityValue: DeviceInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts index 007176ce24..b8539df134 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '@home/components/entity/entity.component'; @@ -42,8 +42,9 @@ export class EdgeComponent extends EntityComponent { protected translate: TranslateService, @Inject('entity') protected entityValue: EdgeInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts index 827e83a7be..38b547a012 100644 --- a/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts +++ b/ui-ngx/src/app/modules/home/pages/entity-view/entity-view.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -53,8 +53,9 @@ export class EntityViewComponent extends EntityComponent { protected translate: TranslateService, @Inject('entity') protected entityValue: EntityViewInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts index 7ea9ef2e3b..e88f2b10a7 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { combineLatest, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -50,8 +50,9 @@ export class OtaUpdateComponent extends EntityComponent implements O protected translate: TranslateService, @Inject('entity') protected entityValue: OtaPackage, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html index 8810956a63..aac4c7b045 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/change-password-dialog.component.html @@ -33,16 +33,19 @@ profile.current-password lock +
login.new-password lock + login.new-password-again lock +
diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts index 3d8eae1e7a..9aa518e4c0 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -37,8 +37,9 @@ export class RuleChainComponent extends EntityComponent { protected translate: TranslateService, @Inject('entity') protected entityValue: RuleChain, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } ngOnInit() { diff --git a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts index 30c0dc36aa..c59a9d557b 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -36,8 +36,9 @@ export class TenantComponent extends ContactBasedComponent { protected translate: TranslateService, @Inject('entity') protected entityValue: TenantInfo, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - protected fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + protected fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } hideDelete() { diff --git a/ui-ngx/src/app/modules/home/pages/user/user.component.ts b/ui-ngx/src/app/modules/home/pages/user/user.component.ts index f6fc69b7d6..7187731b5c 100644 --- a/ui-ngx/src/app/modules/home/pages/user/user.component.ts +++ b/ui-ngx/src/app/modules/home/pages/user/user.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, Optional } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject, Optional } from '@angular/core'; import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -43,8 +43,9 @@ export class UserComponent extends EntityComponent { constructor(protected store: Store, @Optional() @Inject('entity') protected entityValue: User, @Optional() @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } hideDelete() { diff --git a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts index 4947a90e1f..dd3b827c1c 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widgets-bundle.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject } from '@angular/core'; +import { ChangeDetectorRef, Component, Inject } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { EntityComponent } from '../../components/entity/entity.component'; @@ -32,8 +32,9 @@ export class WidgetsBundleComponent extends EntityComponent { constructor(protected store: Store, @Inject('entity') protected entityValue: WidgetsBundle, @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, - public fb: FormBuilder) { - super(store, fb, entityValue, entitiesTableConfigValue); + public fb: FormBuilder, + protected cd: ChangeDetectorRef) { + super(store, fb, entityValue, entitiesTableConfigValue, cd); } hideDelete() { diff --git a/ui-ngx/src/app/modules/login/pages/login/create-password.component.html b/ui-ngx/src/app/modules/login/pages/login/create-password.component.html index 30c8823149..778bb0e409 100644 --- a/ui-ngx/src/app/modules/login/pages/login/create-password.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/create-password.component.html @@ -32,11 +32,13 @@ common.password lock + login.password-again lock +
diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 3fbd055da0..3a475467b6 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -51,12 +51,8 @@ common.password - - + + lock
diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index de66448c73..2526cfe376 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -37,8 +37,7 @@ export class LoginComponent extends PageComponent implements OnInit { password: '' }); oauth2Clients: Array = null; - hidePassword = true; - + constructor(protected store: Store, private authService: AuthService, public fb: FormBuilder, diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html index 3e1923ef37..59d03d7a9a 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password.component.html @@ -35,11 +35,13 @@ login.new-password lock + login.new-password-again lock +
diff --git a/ui-ngx/src/app/shared/components/button/toggle-password.component.html b/ui-ngx/src/app/shared/components/button/toggle-password.component.html new file mode 100644 index 0000000000..69301ce644 --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/toggle-password.component.html @@ -0,0 +1,20 @@ + + diff --git a/ui-ngx/src/app/shared/components/button/toggle-password.component.ts b/ui-ngx/src/app/shared/components/button/toggle-password.component.ts new file mode 100644 index 0000000000..225f4ba023 --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/toggle-password.component.ts @@ -0,0 +1,44 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { AfterViewInit, Component, ElementRef } from '@angular/core'; + +@Component({ + selector: 'tb-toggle-password', + templateUrl: 'toggle-password.component.html', + styleUrls: [], +}) +export class TogglePasswordComponent implements AfterViewInit { + showPassword = false; + hideToggle = false; + + private input: HTMLInputElement = null; + + constructor(private hostElement: ElementRef) { } + + togglePassword($event: Event) { + $event.stopPropagation(); + this.showPassword = !this.showPassword; + this.input.type = this.showPassword ? 'text' : 'password'; + } + + ngAfterViewInit() { + this.input = this.hostElement.nativeElement.closest('mat-form-field').querySelector('input[type="password"]'); + if (this.input === null) { + this.hideToggle = true; + } + } +} diff --git a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts index c2facf99db..fcbdd2d1ca 100644 --- a/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts +++ b/ui-ngx/src/app/shared/models/ace/widget-completion.models.ts @@ -471,6 +471,12 @@ export const widgetContextCompletions: TbEditorCompletions = { description: 'RPC request persistent', type: 'boolean', optional: true + }, + { + name: 'persistentPollingInterval', + description: 'Polling interval in milliseconds to get persistent RPC command response', + type: 'number', + optional: true } ], return: { @@ -504,6 +510,12 @@ export const widgetContextCompletions: TbEditorCompletions = { description: 'RPC request persistent', type: 'boolean', optional: true + }, + { + name: 'persistentPollingInterval', + description: 'Polling interval in milliseconds to get persistent RPC command response', + type: 'number', + optional: true } ], return: { diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index d088cbc88a..1d54b29fa9 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -35,7 +35,8 @@ export enum EntityType { WIDGET_TYPE = 'WIDGET_TYPE', API_USAGE_STATE = 'API_USAGE_STATE', TB_RESOURCE = 'TB_RESOURCE', - OTA_PACKAGE = 'OTA_PACKAGE' + OTA_PACKAGE = 'OTA_PACKAGE', + RPC = 'RPC' } export enum AliasEntityType { diff --git a/ui-ngx/src/app/shared/models/id/public-api.ts b/ui-ngx/src/app/shared/models/id/public-api.ts index 534b50b0f5..3b490a6c97 100644 --- a/ui-ngx/src/app/shared/models/id/public-api.ts +++ b/ui-ngx/src/app/shared/models/id/public-api.ts @@ -26,6 +26,8 @@ export * from './entity-id'; export * from './entity-view-id'; export * from './event-id'; export * from './has-uuid'; +export * from './ota-package-id'; +export * from './rpc-id'; export * from './rule-chain-id'; export * from './rule-node-id'; export * from './tenant-id'; diff --git a/ui-ngx/src/app/shared/models/id/rpc-id.ts b/ui-ngx/src/app/shared/models/id/rpc-id.ts new file mode 100644 index 0000000000..8b3d0b07fd --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/rpc-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class RpcId implements EntityId { + entityType = EntityType.RPC; + id: string; + constructor(id: string) { + this.id = id; + } +} diff --git a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts index 1eba707f08..5edb95e52b 100644 --- a/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts +++ b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -export const LEN_MAX_PSK = 64; +export const LEN_MAX_PSK = 128; export const LEN_MAX_PRIVATE_KEY = 134; export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_X509 = 3000; diff --git a/ui-ngx/src/app/shared/models/public-api.ts b/ui-ngx/src/app/shared/models/public-api.ts index fddcd55033..ddfba7b381 100644 --- a/ui-ngx/src/app/shared/models/public-api.ts +++ b/ui-ngx/src/app/shared/models/public-api.ts @@ -41,6 +41,7 @@ export * from './oauth2.models'; export * from './queue.models'; export * from './relation.models'; export * from './resource.models'; +export * from './rpc.models'; export * from './rule-chain.models'; export * from './rule-node.models'; export * from './settings.models'; diff --git a/ui-ngx/src/app/shared/models/rpc.models.ts b/ui-ngx/src/app/shared/models/rpc.models.ts new file mode 100644 index 0000000000..75ac132394 --- /dev/null +++ b/ui-ngx/src/app/shared/models/rpc.models.ts @@ -0,0 +1,40 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { TenantId } from '@shared/models/id/tenant-id'; +import { RpcId } from '@shared/models/id/rpc-id'; +import { DeviceId } from '@shared/models/id/device-id'; + +export enum RpcStatus { + QUEUED = 'QUEUED', + DELIVERED = 'DELIVERED', + SUCCESSFUL = 'SUCCESSFUL', + TIMEOUT = 'TIMEOUT', + FAILED = 'FAILED' +} + +export interface PersistentRpc { + id: RpcId; + createdTime: number; + expirationTime: number; + status: RpcStatus; + response: any; + request: { + id: string; + }; + deviceId: DeviceId; + tenantId: TenantId; +} diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 3877488671..8b2d4aac78 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -27,6 +27,7 @@ export interface AdminSettings { export declare type SmtpProtocol = 'smtp' | 'smtps'; export interface MailServerSettings { + showChangePassword: boolean; mailFrom: string; smtpProtocol: SmtpProtocol; smtpHost: string; @@ -34,7 +35,8 @@ export interface MailServerSettings { timeout: number; enableTls: boolean; username: string; - password: string; + changePassword?: boolean; + password?: string; enableProxy: boolean; proxyHost: string; proxyPort: number; diff --git a/ui-ngx/src/app/shared/models/time/time.models.ts b/ui-ngx/src/app/shared/models/time/time.models.ts index c9520f09df..561f3a30fc 100644 --- a/ui-ngx/src/app/shared/models/time/time.models.ts +++ b/ui-ngx/src/app/shared/models/time/time.models.ts @@ -812,8 +812,15 @@ export enum TimeUnit { DAYS = 'DAYS' } -export const timeUnitTranslationMap = new Map( +export enum TimeUnitMilli { + MILLISECONDS = 'MILLISECONDS' +} + +export type FullTimeUnit = TimeUnit | TimeUnitMilli; + +export const timeUnitTranslationMap = new Map( [ + [TimeUnitMilli.MILLISECONDS, 'timeunit.milliseconds'], [TimeUnit.SECONDS, 'timeunit.seconds'], [TimeUnit.MINUTES, 'timeunit.minutes'], [TimeUnit.HOURS, 'timeunit.hours'], diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index f2bcdc97a4..02c5824807 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -144,6 +144,7 @@ import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-i import { OtaPackageAutocompleteComponent } from '@shared/components/ota-package/ota-package-autocomplete.component'; import { MAT_DATE_LOCALE } from '@angular/material/core'; import { CopyButtonComponent } from '@shared/components/button/copy-button.component'; +import { TogglePasswordComponent } from '@shared/components/button/toggle-password.component'; @NgModule({ providers: [ @@ -242,7 +243,8 @@ import { CopyButtonComponent } from '@shared/components/button/copy-button.compo ContactComponent, OtaPackageAutocompleteComponent, WidgetsBundleSearchComponent, - CopyButtonComponent + CopyButtonComponent, + TogglePasswordComponent ], imports: [ CommonModule, @@ -415,7 +417,8 @@ import { CopyButtonComponent } from '@shared/components/button/copy-button.compo ContactComponent, OtaPackageAutocompleteComponent, WidgetsBundleSearchComponent, - CopyButtonComponent + CopyButtonComponent, + TogglePasswordComponent ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 3b96068406..b0fc26abe2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -74,7 +74,7 @@ "admin": { "general": "Obecné", "general-settings": "Obecná nastavení", - "home-settings": "Domácí nastavení", + "home-settings": "Nastavení", "outgoing-mail": "Odchozí email", "outgoing-mail-settings": "Nastavení odchozího emailu", "system-settings": "Systémová nastavení", @@ -953,7 +953,7 @@ "credentials-type": "Typ přístupových údajů", "access-token": "Přístupový token", "access-token-required": "Přístupový token je povinný.", - "access-token-invalid": "Délka přístupového tokenu musí být od 1 do 20 znaků.", + "access-token-invalid": "Délka přístupového tokenu musí být od 1 do 32 znaků.", "rsa-key": "RSA veřejný klíč", "rsa-key-required": "RSA veřejný klíč je povinný.", "lwm2m-security-config": { @@ -1229,7 +1229,7 @@ "drx": "Přerušovaný přenos (DRX)", "edrx": "Rozšířený přerušovaný přenos (eDRX)" }, - "edrx-cycle": "eDRX cyklus v milisekudnách", + "edrx-cycle": "eDRX cyklus", "edrx-cycle-required": "eDRX cyklus je povinný.", "edrx-cycle-pattern": "eDRX cyklus musí být kladné číslo.", "lwm2m": { diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index 0e303b260b..eee8d89fd7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -686,7 +686,7 @@ "credentials-type": "Art der Zugangsdaten", "access-token": "Zugangs-Token", "access-token-required": "Zugangs-Token ist erforderlich.", - "access-token-invalid": "Die Länge des Zugangs-Tokens muss zwischen 1 und 20 Zeichen betragen.", + "access-token-invalid": "Die Länge des Zugangs-Tokens muss zwischen 1 und 32 Zeichen betragen.", "rsa-key": "RSA öffentlicher Schlüssel", "rsa-key-required": "RSA öffentlicher Schlüssel ist erforderlich.", "secret": "Geheimnis", @@ -803,7 +803,7 @@ "rulechain-templates": "Regelkettenvorlagen", "rulechains": "Rand Regelketten", "search": "Kanten durchsuchen", - "selected-edges": "{Anzahl, Plural, 1 {1 Kante} andere {# Kanten}} ausgewählt", + "selected-edges": "{count, plural, 1 {1 Rand} other {# Rand} } ausgewählt", "any-edge": "Beliebige Kante", "no-edge-types-matching": "Es wurden keine Kantentypen gefunden, die mit '{{entitySubtype}}' übereinstimmen.", "edge-type-list-empty": "Keine Kantentypen ausgewählt.", @@ -1452,7 +1452,7 @@ "unset-auto-assign-to-edge-text": "Nach der Bestätigung wird die Kantenregelkette bei der Erstellung nicht mehr automatisch den Kanten zugewiesen.", "edge-template-root": "Vorlagenstamm", "search": "Suchen Sie nach Regelketten", - "selected-rulechains": "{count, plural, 1 {1 Regelkette} andere {# Regelketten}} ausgewählt", + "selected-rulechains": "{count, plural, 1 {1 Regelkette} other {# Regelketten} } ausgewählt", "open-rulechain": "Regelkette öffnen", "assign-to-edge": "Rand zuweisen", "edge-rulechain": "Kantenregelkette" diff --git a/ui-ngx/src/assets/locale/locale.constant-el_GR.json b/ui-ngx/src/assets/locale/locale.constant-el_GR.json index d92b6e7884..153621e69a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-el_GR.json +++ b/ui-ngx/src/assets/locale/locale.constant-el_GR.json @@ -798,7 +798,7 @@ "credentials-type": "Τύπος διαπιστευτηρίων", "access-token": "Διακριτικό πρόσβασης", "access-token-required": "Απαιτείται διακριτικό πρόσβασης.", - "access-token-invalid": "Access token length must be from 1 to 20 characters.", + "access-token-invalid": "Access token length must be from 1 to 32 characters.", "rsa-key": "RSA public key", "rsa-key-required": "Απαιτείται RSA public key.", "secret": "Secret", diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 854d99f603..211a1b5f78 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -104,6 +104,7 @@ "proxy-port-range": "Proxy port should be in a range from 1 to 65535.", "proxy-user": "Proxy user", "proxy-password": "Proxy password", + "change-password": "Change password", "send-test-mail": "Send test mail", "sms-provider": "SMS provider", "sms-provider-settings": "SMS provider settings", @@ -956,7 +957,7 @@ "credentials-type": "Credentials type", "access-token": "Access token", "access-token-required": "Access token is required.", - "access-token-invalid": "Access token length must be from 1 to 20 characters.", + "access-token-invalid": "Access token length must be from 1 to 32 characters.", "rsa-key": "RSA public key", "rsa-key-required": "RSA public key is required.", "lwm2m-security-config": { @@ -1232,15 +1233,18 @@ "drx": "Discontinuous Reception (DRX)", "edrx": "Extended Discontinuous Reception (eDRX)" }, - "edrx-cycle": "eDRX cycle in milliseconds", + "edrx-cycle": "eDRX cycle", "edrx-cycle-required": "eDRX cycle is required.", "edrx-cycle-pattern": "eDRX cycle must be a positive integer.", - "paging-transmission-window": "Paging Transmission Window in milliseconds", + "edrx-cycle-min": "Minimum number of eDRX cycle is {{ min }} seconds.", + "paging-transmission-window": "Paging Transmission Window", "paging-transmission-window-required": "Paging transmission window is required.", "paging-transmission-window-pattern": "Paging transmission window must be a positive integer.", - "psm-activity-timer": "PSM Activity Timer in milliseconds", + "paging-transmission-window-min": "Minimum number ofpPaging transmission window is {{ min }} seconds.", + "psm-activity-timer": "PSM Activity Timer", "psm-activity-timer-required": "PSM activity timer is required.", "psm-activity-timer-pattern": "PSM activity timer must be a positive integer.", + "psm-activity-timer-min": "Minimum number of PSM activity timer is {{ min }} seconds.", "lwm2m": { "object-list": "Object list", "object-list-empty": "No objects selected.", @@ -2757,6 +2761,7 @@ } }, "timeunit": { + "milliseconds": "Milliseconds", "seconds": "Seconds", "minutes": "Minutes", "hours": "Hours", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index d762dd17a6..a782298bdc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -413,8 +413,8 @@ "unassign-asset-from-edge": "Anular activo de bodre", "unassign-asset-from-edge-title": "¿Está seguro de que desea desasignar el activo '{{assetName}}'?", "unassign-asset-from-edge-text": "Después de la confirmación, el activo no será asignado y el borde no podrá acceder a él", - "unassign-assets-from-edge-action-title": "Anular asignación {count, plural, 1 {1 activo} other {# activos}} desde el borde", - "unassign-assets-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, 1 {1 activo} other {# activos}}?", + "unassign-assets-from-edge-action-title": "Anular asignación {count, plural, 1 {1 activo} other {# activos} } desde el borde", + "unassign-assets-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, 1 {1 activo} other {# activos} }?", "unassign-assets-from-edge-text": "Después de la confirmación, todos los activos seleccionados quedarán sin asignar y el borde no podrá acceder a ellos." }, "attribute": { @@ -893,7 +893,7 @@ "credentials-type": "Tipo de credencial", "access-token": "Tóken de acceso", "access-token-required": "Access token requerido.", - "access-token-invalid": "Access token debe tener entre 1 a 20 caracteres.", + "access-token-invalid": "Access token debe tener entre 1 a 32 caracteres.", "rsa-key": "Clave pública RSA", "rsa-key-required": "Clave pública RSA requerida.", "client-id": "ID Cliente", @@ -950,7 +950,7 @@ "assign-device-to-edge-text": "Seleccione los dispositivos para asignar al borde", "unassign-device-from-edge-title": "¿Está seguro de que desea desasignar el dispositivo '{{deviceName}}'?", "unassign-device-from-edge-text": "Después de la confirmación, el dispositivo no será asignado y el borde no podrá acceder a él", - "unassign-devices-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, 1 {1 dispositivo} other {# dispositivos}}?", + "unassign-devices-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, 1 {1 dispositivo} other {# dispositivos} }?", "unassign-devices-from-edge-text": "Después de la confirmación, todos los dispositivos seleccionados quedarán sin asignar y el borde no podrá acceder a ellos." }, "device-profile": { @@ -1123,7 +1123,7 @@ "delete": "Eliminar borde", "delete-edge-title": "¿Está seguro de que desea eliminar el borde '{{edgeName}}'?", "delete-edge-text": "Tenga cuidado, después de la confirmación, el borde y todos los datos relacionados serán irrecuperables", - "delete-edges-title": "¿Está seguro de que desea edge {count, plural, 1 {1 borde} other {# bordes}}?", + "delete-edges-title": "¿Está seguro de que desea edge {count, plural, 1 {1 borde} other {# bordes} }?", "delete-edges-text": "Tenga cuidado, después de la confirmación se eliminarán todos los bordes seleccionados y todos los datos relacionados se volverán irrecuperables", "name": "Nombre", "name-starts-with": "Edge name starts with", @@ -1156,7 +1156,7 @@ "unassign-from-customer": "Anular asignación del cliente", "unassign-edge-title": "¿Está seguro de que desea desasignar el borde '{{edgeName}}'?", "unassign-edge-text": "Después de la confirmación, el borde quedará sin asignar y el cliente no podrá acceder a él", - "unassign-edges-title": "¿Está seguro de que desea anular la asignación de {count, plural, 1 {1 borde} other {# bordes}}?", + "unassign-edges-title": "¿Está seguro de que desea anular la asignación de {count, plural, 1 {1 borde} other {# bordes} }?", "unassign-edges-text": "Después de la confirmación de todos los bordes seleccionados, se anulará la asignación y el cliente no podrá acceder a ellos.", "make-public": "Hacer público el borde", "make-public-edge-title": "¿Estás seguro de que quieres hacer público el edge '{{edgeName}}'?", @@ -1189,14 +1189,14 @@ "rulechain-templates": "Plantillas, de cadena de reglas", "rulechains": "Cadenas de regla de borde", "search": "Bordes de búsqueda", - "selected-edges": "{count, plural, 1 {1 borde} other {# bordes}} seleccionados", + "selected-edges": "{count, plural, 1 {1 borde} other {# bordes} } seleccionadas", "any-edge": "Cualquier bordee", "no-edge-types-matching": "No se encontraron tipos de aristas que coincidan con '{{entitySubtype}}'.", "edge-type-list-empty": "No se seleccionó ningún tipo de borde.", "edge-types": "Tipos de bordes", "enter-edge-type": "Ingrese el tipo de borde", "deployed": "Desplegada", - "pending": "Pending", + "pending": "Pendiente", "downlinks": "Enlaces descendentes", "no-downlinks-prompt": "No se encontraron enlaces descendentes", "sync-process-started-successfully": "¡El proceso de sincronización se inició correctamente!", @@ -1356,7 +1356,7 @@ "type-api-usage-state": "Estado de uso de la API", "type-edge": "Borde", "type-edges": "Bordes", - "list-of-edges": "{cuenta, plural, 1 {Un borde} other {Lista de # bordes}}", + "list-of-edges": "{count, plural, 1 {Un borde} other {Lista de # bordes} }", "edge-name-starts-with": "Bordes cuyos nombres comienzan con '{{prefijo}}'" }, "entity-field": { @@ -1481,9 +1481,9 @@ "assign-entity-view-to-edge-text": "Seleccione las vistas de entidad para asignar al borde", "unassign-entity-view-from-edge-title": "¿Está seguro de que desea anular la asignación de la vista de entidad '{{entityViewName}}'?", "unassign-entity-view-from-edge-text": "Después de la confirmación, la vista de entidad quedará sin asignar y el borde no podrá acceder a ella", - "unassign-entity-views-from-edge-action-title": "Anular asignación {recuento, plural, 1 {1 vista de entidad} otras {# vistas de entidad}} del borde", + "unassign-entity-views-from-edge-action-title": "Anular asignación {count, plural, 1 {1 vista de entidad} other {# vistas de entidad} } del borde", "unassign-entity-view-from-edge": "Anular asignación de vista de entidad", - "unassign-entity-views-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, 1 {1 vista de entidad} other {# vistas de entidad}}?", + "unassign-entity-views-from-edge-title": "¿Está seguro de que desea desasignar {count, plural, 1 {1 vista de entidad} other {# vistas de entidad} }?", "unassign-entity-views-from-edge-text": "Después de la confirmación, todas las vistas de entidad seleccionadas no serán asignadas y el borde no podrá acceder a ellas" }, "event": { @@ -2074,9 +2074,9 @@ "delete-rulechains": "Eliminar cadenas de reglas", "unassign-rulechain": "Anular asignación de cadena de reglas", "unassign-rulechains": "Anular asignación de cadenas de reglas", - "unassign-rulechain-title": "¿Está seguro de que desea desasignar la cadena de reglas '{{ruleChainTitle}}'?", + "unassign-rulechain-title": "¿Está seguro de que desea desasignar la cadena de reglas '{{ruleChainName}}'?", "unassign-rulechain-from-edge-text": "Después de la confirmación, la cadena de reglas quedará sin asignar y el borde no podrá acceder a ella", - "unassign-rulechains-from-edge-action-title": "Anular asignación {count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas}} des bordes", + "unassign-rulechains-from-edge-action-title": "Anular asignación {count, plural, 1 {1 cadena de reglas} other {# cadenas de reglas} } des bordes", "unassign-rulechains-from-edge-text": "Después de la confirmación, todas las cadenas de reglas seleccionadas quedarán sin asignar y el borde no podrá acceder a ellas", "assign-rulechain-to-edge-title": "Asignar cadena (s) de reglas a borde", "assign-rulechain-to-edge-text": "Seleccione las cadenas de reglas para asignar al borde", diff --git a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json index 90a10f765c..8b74ce6f79 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fa_IR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fa_IR.json @@ -640,7 +640,7 @@ "credentials-type": "نوع اعتبارنامه ها", "access-token": "شناسه دسترسي", "access-token-required": ".شناسه دسترسي مورد نياز است", - "access-token-invalid": ".طول شناسه دسترسي بايد از 1 تا 20 حرف باشد", + "access-token-invalid": ".طول شناسه دسترسي بايد از 1 تا 32 حرف باشد", "rsa-key": "RSA کليد عمومي", "rsa-key-required": ".مورد نياز است RSA کليد عمومي", "secret": "محرمانه", diff --git a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json index 00f9c95b83..facf0f8534 100644 --- a/ui-ngx/src/assets/locale/locale.constant-fr_FR.json +++ b/ui-ngx/src/assets/locale/locale.constant-fr_FR.json @@ -639,7 +639,7 @@ }, "device": { "access-token": "Jeton d'accès", - "access-token-invalid": "La longueur du jeton d'accès doit être comprise entre 1 et 20 caractéres.", + "access-token-invalid": "La longueur du jeton d'accès doit être comprise entre 1 et 32 caractéres.", "access-token-required": "Le jeton d'accès est requis.", "accessTokenCopiedMessage": "Le jeton d'accès au dispositif a été copié dans le presse-papier", "add": "Ajouter un dispositif", @@ -736,7 +736,7 @@ "assign-device-to-edge-text":"Veuillez sélectionner la bordure pour attribuer le ou les dispositifs", "unassign-device-from-edge-title": "Êtes-vous sûr de vouloir annuler l'affection du dispositif {{deviceName}} '?", "unassign-device-from-edge-text": "Après la confirmation, dispositif sera non attribué et ne sera pas accessible a la bordure.", - "unassign-devices-from-edge-title": "Voulez-vous vraiment annuler l'affectation de {count, plural, 1 {1 device} other {# devices}}?", + "unassign-devices-from-edge-title": "Voulez-vous vraiment annuler l'affectation de {count, plural, 1 {1 device} other {# devices} }?", "unassign-devices-from-edge-text": "Après la confirmation, tous les dispositifs sélectionnés ne seront pas attribues et ne seront pas accessibles par la bordure." }, "dialog": { @@ -755,7 +755,7 @@ "delete": "Supprimer la bordure", "delete-edge-title": "Êtes-vous sûr de vouloir supprimer la bordure '{{edgeName}}'?", "delete-edge-text": "Faites attention, après la confirmation, la bordure et toutes les données associées deviendront irrécupérables", - "delete-edges-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 bordure} other {# bordure}}?", + "delete-edges-title": "Êtes-vous sûr de vouloir supprimer {count, plural, 1 {1 bordure} other {# bordure} }?", "delete-edges-text": "Faites attention, après la confirmation, tous les bordures sélectionnés seront supprimés et toutes les données associées deviendront irrécupérables.", "name": "Nom", "name-starts-with": "Le nom du bord commence par", @@ -788,7 +788,7 @@ "unassign-from-customer": "Retirer du client", "unassign-edge-title": "Êtes-vous sûr de vouloir annuler l'affection du dispositif {{edgeName}}", "unassign-edge-text": "Après la confirmation, le dispositif ne sera pas attribué et ne sera pas accessible au client", - "unassign-edges-title": "Voulez-vous vraiment annuler l'attribution de {count, plural, 1 {1 bordure} other {# bordures}}?", + "unassign-edges-title": "Voulez-vous vraiment annuler l'attribution de {count, plural, 1 {1 bordure} other {# bordures} }?", "unassign-edges-text": "Après la confirmation, tous les bordures sélectionnés ne seront plus attribués et ne seront pas accessibles par le client.", "make-public": "Make edge public", "make-public-edge-title": "Are you sure you want to make the edge '{{edgeName}}' public?", @@ -821,7 +821,7 @@ "rulechain-templates": "Modèles de chaîne de règles", "rulechains": "Chaînes de règles de la bordure", "search": "Rechercher les bords", - "selected-edges": "{count, plural, 1 {1 edge} other {# bords}} sélectionné", + "selected-edges": "{count, plural, 1 {1 bordure} other {# bords} } sélectionné", "any-edge": "Tout bord", "no-edge-types-matching": "Aucun type d'arête correspondant à \"{{entitySubtype}}\" n'a été trouvé.", "edge-type-list-empty": "Aucun type d'arête sélectionné.", @@ -1479,9 +1479,9 @@ "delete-rulechains": "Supprimer une chaînes de règles", "unassign-rulechain": "Retirer chaîne de règles", "unassign-rulechains": "Retirer chaînes de règles", - "unassign-rulechain-title": "AÊtes-vous sûr de vouloir retirer l'attribution de chaînes de règles '{{ruleChainTitle}}'?", + "unassign-rulechain-title": "AÊtes-vous sûr de vouloir retirer l'attribution de chaînes de règles '{{ruleChainName}}'?", "unassign-rulechain-from-edge-text": "Après la confirmation, l'actif sera non attribué et ne sera pas accessible a la bordure.", - "unassign-rulechains-from-edge-action-title": "Retirer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles}} de la bordure", + "unassign-rulechains-from-edge-action-title": "Retirer {count, plural, 1 {1 chaîne de règles} other {# chaînes de règles} } de la bordure", "unassign-rulechains-from-edge-text": "Après la confirmation, tous les chaînes de règles sélectionnés ne seront pas attribués et ne seront pas accessibles a la bordure.", "assign-rulechain-to-edge-title": "Attribuer les chaînes de règles a la bordure", "assign-rulechain-to-edge-text": "Veuillez sélectionner la bordure pour attribuer le ou les chaînes de règles", @@ -1497,11 +1497,11 @@ "unset-auto-assign-to-edge-text": "Après la confirmation, la chaîne de règles d'arêtes ne sera plus automatiquement affectée aux arêtes lors de la création.", "edge-template-root": "Racine du modèle", "search": "Rechercher des chaînes de règles", - "selected-rulechains": "{count, plural, 1 {1 rule chain} other {# rule chains}} sélectionné", + "selected-rulechains": "{count, plural, 1 {1 rule chain} other {# rule chains} } sélectionné", "open-rulechain": "Chaîne de règles ouverte", - "assign-to-edge": "Attribuer à Edge", - "edge-rulechain": "Chaîne de règles Edge", - "unassign-rulechains-from-edge-title": "Voulez-vous vraiment annuler l'attribution de {count, plural, 1 {1 rulechain} other {# rulechains}}?" + "assign-to-edge": "Attribuer à Bordure", + "edge-rulechain": "Chaîne de règles Bordure", + "unassign-rulechains-from-edge-title": "Voulez-vous vraiment annuler l'attribution de {count, plural, 1 {1 rulechain} other {# rulechains} }?" }, "rulenode": { "add": "Ajouter un noeud de règle", diff --git a/ui-ngx/src/assets/locale/locale.constant-it_IT.json b/ui-ngx/src/assets/locale/locale.constant-it_IT.json index 4050e48ad7..2e5ace98a7 100644 --- a/ui-ngx/src/assets/locale/locale.constant-it_IT.json +++ b/ui-ngx/src/assets/locale/locale.constant-it_IT.json @@ -665,7 +665,7 @@ "credentials-type": "Tipo credenziali", "access-token": "Token di accesso", "access-token-required": "Token di accesso obbligatorio.", - "access-token-invalid": "Il token di accesso deve avere una lunghezza compresa tra 1 e 20 caratteri.", + "access-token-invalid": "Il token di accesso deve avere una lunghezza compresa tra 1 e 32 caratteri.", "rsa-key": "Chiave pubblica RSA", "rsa-key-required": "Chiave pubblica RSA obbligatoria.", "secret": "Secret", diff --git a/ui-ngx/src/assets/locale/locale.constant-ja_JA.json b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json index f3696e9a23..4b9da9e41f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ja_JA.json +++ b/ui-ngx/src/assets/locale/locale.constant-ja_JA.json @@ -623,7 +623,7 @@ "credentials-type": "資格情報タイプ", "access-token": "アクセストークン", "access-token-required": "アクセストークンが必要です。", - "access-token-invalid": "アクセストークンの長さは、1〜20文字でなければなりません。", + "access-token-invalid": "アクセストークンの長さは、1〜32文字でなければなりません。", "rsa-key": "RSA公開鍵", "rsa-key-required": "RSA公開鍵が必要です。", "secret": "秘密", diff --git a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json index 6f5ba73674..f18d62cad2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ka_GE.json +++ b/ui-ngx/src/assets/locale/locale.constant-ka_GE.json @@ -683,7 +683,7 @@ "credentials-type": "მომხმარებლის ჩანაწერი", "access-token": "ტოკენი", "access-token-required": "ტოკენი სავალდებულოა", - "access-token-invalid": "ტოკენის სიგრძე უნდა იყოს 1 დან 20 სიმბოლომდე", + "access-token-invalid": "ტოკენის სიგრძე უნდა იყოს 1 დან 32 სიმბოლომდე", "rsa-key": "ღია გასაღები RSA", "rsa-key-required": "ღია გასაღები RSA აუცილებელია", "secret": "საიდუმლო", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index f966203fbf..56ae7d0fc3 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -869,7 +869,7 @@ "credentials-type": "크리덴셜 타입", "access-token": "억세스 토큰", "access-token-required": "액세스 토큰을 입력하세요.", - "access-token-invalid": "액세스 토큰 길이는 1 - 20 자 여야합니다.", + "access-token-invalid": "액세스 토큰 길이는 1 - 32 자 여야합니다.", "rsa-key": "RSA public key", "rsa-key-required": "RSA public key 를 입력하세요.", "client-id": "클라이언트 ID", diff --git a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json index c2b7ce6e79..7daa8e5802 100644 --- a/ui-ngx/src/assets/locale/locale.constant-lv_LV.json +++ b/ui-ngx/src/assets/locale/locale.constant-lv_LV.json @@ -641,7 +641,7 @@ "credentials-type": "Akreditācijas datu tips", "access-token": "Piekļuves tokens", "access-token-required": "Piekļuves tokens ir nepieciešams.", - "access-token-invalid": "Piekļuves tokena garumam ir jābūt no 1 līdz 20 rakstzīmēm.", + "access-token-invalid": "Piekļuves tokena garumam ir jābūt no 1 līdz 32 rakstzīmēm.", "rsa-key": "RSA publiskā atslēga", "rsa-key-required": "RSA publiskā atslēga ir nepieciešama.", "secret": "Noslēpums", diff --git a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json index 7f5c7ce239..7c8e1e7449 100644 --- a/ui-ngx/src/assets/locale/locale.constant-pt_BR.json +++ b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json @@ -715,7 +715,7 @@ "credentials-type": "Tipo de credencial", "access-token": "Token de acesso", "access-token-required": "O token de acesso é obrigatório.", - "access-token-invalid": "O token de acesso deve ter de 1 a 20 caracteres.", + "access-token-invalid": "O token de acesso deve ter de 1 a 32 caracteres.", "rsa-key": "Chave pública RSA", "rsa-key-required": "A chave pública RSA é obrigatória.", "secret": "Segredo", diff --git a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json index 80c9941f2a..9e86bd1a84 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ro_RO.json +++ b/ui-ngx/src/assets/locale/locale.constant-ro_RO.json @@ -674,7 +674,7 @@ "credentials-type": "Tip Credențiale", "access-token": "Token Acces", "access-token-required": "Tokenul de acces este necesar", - "access-token-invalid": "Dimensiunea tokenului de acces trebuie să fie de 1-20 caractere", + "access-token-invalid": "Dimensiunea tokenului de acces trebuie să fie de 1-32 caractere", "rsa-key": "Cheie RSA publică", "rsa-key-required": "Cheia RSA publică key este necesară", "secret": "Cod Secret", @@ -1797,4 +1797,4 @@ "language": { "language": "Limba" } -} \ No newline at end of file +} diff --git a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json index ba1a8e1a7b..9ba75064db 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -677,7 +677,7 @@ "credentials-type": "Тип учетных данных", "access-token": "Токен", "access-token-required": "Токен обязателен.", - "access-token-invalid": "Длина токена должна быть от 1 до 20 символов.", + "access-token-invalid": "Длина токена должна быть от 1 до 32 символов.", "rsa-key": "Открытый ключ RSA", "rsa-key-required": "Открытый ключ RSA обязателен.", "secret": "Секрет", diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index e57ee78e87..8016988f8d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -869,7 +869,7 @@ "credentials-type": "Vrsta poverilnic", "access-token": "Dostopni žeton", "access-token-required": "Zahtevan je žeton za dostop.", - "access-token-invalid": "Dolžina žetona za dostop mora biti od 1 do 20 znakov.", + "access-token-invalid": "Dolžina žetona za dostop mora biti od 1 do 32 znakov.", "rsa-key": "Javni ključ RSA", "rsa-key-required": "Potreben je javni ključ RSA.", "client-id": "Client ID", diff --git a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json index dfe3dbbaef..bb27e3b89e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-tr_TR.json +++ b/ui-ngx/src/assets/locale/locale.constant-tr_TR.json @@ -667,7 +667,7 @@ "credentials-type": "Kimlik Bilgi Türü", "access-token": "Erişim şifresi", "access-token-required": "Erişim şifresi gerekli.", - "access-token-invalid": "Erişim şifresi uzunluğu 1 ile 20 karakter arasında olmalıdır.", + "access-token-invalid": "Erişim şifresi uzunluğu 1 ile 32 karakter arasında olmalıdır.", "rsa-key": "RSA açık anahtarı", "rsa-key-required": "RSA açık anahtarı gerekli.", "secret": "Secret", diff --git a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json index 5d4a917335..23bb56cf29 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -794,7 +794,7 @@ "credentials-type": "Тип авторизаційних даних", "access-token": "Маркер доступу", "access-token-required": "Необхідно вказати маркер доступу.", - "access-token-invalid": "Маркер доступу має бути від 1 до 20 символів.", + "access-token-invalid": "Маркер доступу має бути від 1 до 32 символів.", "rsa-key": "Публічний ключ RSA", "rsa-key-required": "Необхідно вказати публічний ключ RSA.", "secret": "Секрет", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index cc23f5fc78..cb135a221a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -996,7 +996,7 @@ }, "device": { "access-token": "访问令牌", - "access-token-invalid": "访问令牌长度必须为1到20个字符。", + "access-token-invalid": "访问令牌长度必须为1到32个字符。", "access-token-required": "访问令牌必填", "accessTokenCopiedMessage": "设备访问令牌已复制到剪贴板", "add": "添加设备", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json index bc8374a1c1..59bceb7b26 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_TW.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_TW.json @@ -624,7 +624,7 @@ "credentials-type": "憑據類型", "access-token": "存取令牌", "access-token-required": "需要存取令牌", - "access-token-invalid": "存取令牌長度必須為1到20個字符。", + "access-token-invalid": "存取令牌長度必須為1到32個字符。", "rsa-key": "RSA公鑰", "rsa-key-required": "需要RSA公鑰", "secret": "密鑰",