diff --git a/application/pom.xml b/application/pom.xml index cd8313c220..98a65bc5bb 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard application @@ -249,6 +249,10 @@ io.grpc grpc-stub + + org.opensmpp + opensmpp-core + org.thingsboard springfox-boot-starter diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index e852bcc9d5..4e71606cfd 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -54,9 +54,10 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", + "latestDataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"LatestDataKeySettings\",\n \"properties\": {\n \"show\": {\n \"title\": \"Show latest data column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"order\": {\n \"title\": \"Latest data column order\",\n \"type\": \"number\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"show\",\n {\n \"key\": \"order\",\n \"condition\": \"model.show === true\"\n },\n {\n \"key\": \"useCellStyleFunction\",\n \"condition\": \"model.show === true\"\n },\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.show === true && model.useCellStyleFunction === true\"\n },\n {\n \"key\": \"useCellContentFunction\",\n \"condition\": \"model.show === true\"\n },\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.show === true && model.useCellContentFunction === true\"\n }\n ]\n}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}" } }, diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 602fdb3c6b..4313a21698 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -146,7 +146,7 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" @@ -164,10 +164,11 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" + "latestDataKeySettingsSchema": "{}", + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}" } }, { @@ -182,10 +183,10 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n", "settingsSchema": "{}", "dataKeySettingsSchema": "{}", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}" } } ] diff --git a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json index 5fdced278b..4e5115bb98 100644 --- a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json @@ -21,7 +21,7 @@ "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}", - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityLabel\":false,\"useRowStyleFunction\":false},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Add device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"useShowWidgetActionFunction\":null,\"showWidgetActionFunction\":\"return true;\",\"type\":\"customPretty\",\"customHtml\":\"
\\n \\n

Edit device

\\n \\n \\n
\\n \\n \\n
\\n
\\n
\\n \\n Device name\\n \\n \\n Device name is required.\\n \\n \\n
\\n \\n \\n Label\\n \\n \\n
\\n
\\n \\n Latitude\\n \\n \\n \\n Longitude\\n \\n \\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n if (vm.editDeviceFormGroup.get('deviceType').value !== vm.device.type) {\\n delete vm.device.deviceProfileId;\\n }\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"openInSeparateDialog\":false,\"openInPopover\":false,\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}" } }, { diff --git a/application/src/main/data/json/system/widget_bundles/maps.json b/application/src/main/data/json/system/widget_bundles/maps.json index 24f093f36b..d2151ecee0 100644 --- a/application/src/main/data/json/system/widget_bundles/maps.json +++ b/application/src/main/data/json/system/widget_bundles/maps.json @@ -18,7 +18,7 @@ "resources": [], "templateHtml": "", "templateCss": ".error {\n color: red;\n}\n.tb-labels {\n color: #222;\n font: 12px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n text-align: center;\n width: 200px;\n white-space: nowrap;\n}", - "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('tencent-map', true, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('tencent-map', true);\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('tencent-map', true);\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true\n };\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('tencent-map', true, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.map.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('tencent-map', true);\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('tencent-map', true);\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true\n };\n}", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First route\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.5851719234007373,\"funcBody\":\"var lats = [37.7696499,\\n37.7699074,\\n37.7699536,\\n37.7697242,\\n37.7695189,\\n37.7696889,\\n37.7697153,\\n37.7701244,\\n37.7700604,\\n37.7705491,\\n37.7715705,\\n37.771752,\\n37.7707533,\\n37.769866];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lats[i];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.9015113051937396,\"funcBody\":\"var lons = [-122.4261215,\\n-122.4219157,\\n-122.4199623,\\n-122.4179074,\\n-122.4155876,\\n-122.4155521,\\n-122.4163203,\\n-122.4193876,\\n-122.4210496,\\n-122.422284,\\n-122.4232717,\\n-122.4235138,\\n-122.4247605,\\n-122.4258812];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lons[i];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7253460349565717,\"funcBody\":\"var value = prevValue;\\nif (time % 500 < 100) {\\n value = value + Math.random() * 40 - 20;\\n if (value < 45) {\\n \\tvalue = 45;\\n } else if (value > 130) {\\n \\tvalue = 130;\\n }\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"
${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
Speed: ${Speed} MPH
See advanced settings for details
\",\"markerImageSize\":34,\"useColorFunction\":true,\"markerImages\":[\"data:image/png;base64,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\",\"data:image/png;base64,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\",\"data:image/png;base64,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\"],\"useMarkerImageFunction\":true,\"colorFunction\":\"var speed = dsData[dsIndex]['Speed'];\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n if (percent < 0.5) {\\n percent *=2*100; \\n return tinycolor.mix('green', 'yellow', amount = percent).toHexString();\\n } else {\\n percent = (percent - 0.5)*2*100;\\n return tinycolor.mix('yellow', 'red', amount = percent).toHexString();\\n }\\n}\",\"markerImageFunction\":\"var speed = dsData[dsIndex]['Speed'];\\nvar res = {\\n url: images[0],\\n size: 55\\n};\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n var index = Math.min(2, Math.floor(3 * percent));\\n res.url = images[index];\\n}\\nreturn res;\",\"strokeWeight\":4,\"strokeOpacity\":0.65,\"color\":\"#1976d3\",\"tmDefaultMapType\":\"roadmap\",\"showTooltip\":true,\"autocloseTooltip\":true,\"tmApiKey\":\"84d6d83e0e51e481e50454ccbe8986b\",\"labelFunction\":\"var vehicleType = dsData[dsIndex]['vehicleType'];\\r\\nif (typeof vehicleType !== undefined) {\\r\\n if (vehicleType == \\\"bus\\\") {\\r\\n return 'Bus: ${entityName}';\\r\\n } else if (vehicleType == \\\"car\\\") {\\r\\n return 'Car: ${entityName}';\\r\\n }\\r\\n}\",\"tooltipFunction\":\"var vehicleType = dsData[dsIndex]['vehicleType'];\\r\\nif (typeof vehicleType !== undefined) {\\r\\n if (vehicleType == \\\"bus\\\") {\\r\\n return 'Bus: ${entityName}
Bus route: ${busRoute}
';\\r\\n } else if (vehicleType == \\\"car\\\") {\\r\\n return 'Car: ${entityName}
Current destination: ${destination}
';\\r\\n }\\r\\n}\",\"provider\":\"tencent-map\",\"defaultCenterPosition\":\"0,0\",\"showTooltipAction\":\"click\",\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"draggableMarker\":false,\"disableScrollZooming\":false,\"disableZoomControl\":false,\"useLabelFunction\":false,\"useTooltipFunction\":false,\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"showPolygon\":false,\"showCircle\":false},\"title\":\"Route Map - Tencent Maps\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" @@ -36,7 +36,7 @@ "resources": [], "templateHtml": "", "templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n", - "controllerScript": "self.onInit = function() {\n var $scope = self.ctx.$scope;\n $scope.self = self;\n}\n\nself.actionSources = function() {\n return {\n 'tooltipAction': {\n name: 'widget-action.tooltip-tag-action',\n multiple: false\n }\n }\n};\n\nself.getSettingsSchema = function() {\n return TbTripAnimationWidget.getSettingsSchema();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true\n };\n}", + "controllerScript": "self.onInit = function() {\n var $scope = self.ctx.$scope;\n $scope.self = self;\n}\n\nself.actionSources = function() {\n return {\n 'tooltipAction': {\n name: 'widget-action.tooltip-tag-action',\n multiple: false\n }\n }\n};\n\nself.getSettingsSchema = function() {\n return TbTripAnimationWidget.getSettingsSchema();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true\n };\n}", "settingsSchema": "", "dataKeySettingsSchema": "{}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"entityAliasId\":null,\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 0 : (value + 2) % gpsData.length)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var gpsData = [\\n37.771210000, -122.510960000,\\n 37.771990000, -122.497070000,\\n 37.772730000, -122.480740000,\\n 37.773360000, -122.466870000,\\n 37.774270000, -122.458520000,\\n 37.771980000, -122.454110000,\\n 37.768250000, -122.453380000,\\n 37.765920000, -122.456810000,\\n 37.765930000, -122.467680000,\\n 37.765500000, -122.477180000,\\n 37.765300000, -122.481660000,\\n 37.764780000, -122.493350000,\\n 37.764120000, -122.508360000,\\n 37.766410000, -122.510260000,\\n 37.770010000, -122.510830000,\\n 37.770980000, -122.510930000\\n];\\n let value = gpsData.indexOf(prevValue); \\nreturn gpsData[(value == -1 ? 1 : (value + 2) % gpsData.length)];\",\"units\":null,\"decimals\":null,\"usePostProcessing\":null,\"postFuncBody\":null}]}],\"timewindow\":{\"history\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":500}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"mapProvider\":\"OpenStreetMap.Mapnik\",\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"showTooltip\":true,\"tooltipColor\":\"#fff\",\"tooltipFontColor\":\"#000\",\"tooltipOpacity\":1,\"tooltipPattern\":\"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
End Time: ${maxTime}
Start Time: ${minTime}\",\"strokeWeight\":2,\"strokeOpacity\":1,\"pointSize\":10,\"markerImageSize\":34,\"rotationAngle\":180,\"provider\":\"openstreet-map\",\"normalizationStep\":1000,\"decoratorSymbol\":\"arrowHead\",\"decoratorSymbolSize\":10,\"decoratorCustomColor\":\"#000\",\"decoratorOffset\":\"20px\",\"endDecoratorOffset\":\"20px\",\"decoratorRepeat\":\"20px\",\"polygonTooltipPattern\":\"${entityName}

TimeStamp: ${ts:7}\",\"polygonOpacity\":0.5,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":1,\"pointTooltipOnRightPanel\":true,\"autocloseTooltip\":true,\"useCustomProvider\":false,\"useLabelFunction\":false,\"useTooltipFunction\":false,\"useMarkerImageFunction\":false,\"useColorFunction\":false,\"usePolylineDecorator\":false,\"useDecoratorCustomColor\":false,\"showPoints\":false,\"showPolygon\":false},\"title\":\"Trip Animation\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":false,\"showLegend\":false,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false},\"displayTimewindow\":true}" @@ -54,7 +54,7 @@ "resources": [], "templateHtml": "", "templateCss": ".error {\n color: red;\n}\n.tb-labels {\n color: #222;\n font: 12px/1.5 \"Helvetica Neue\", Arial, Helvetica, sans-serif;\n text-align: center;\n width: 200px;\n white-space: nowrap;\n}", - "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('google-map', true, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('google-map', true);\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('google-map', true);\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true\n };\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('google-map', true, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.map.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('google-map', true);\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('google-map', true);\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true\n };\n}", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First route\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.5851719234007373,\"funcBody\":\"var lats = [37.7696499,\\n37.7699074,\\n37.7699536,\\n37.7697242,\\n37.7695189,\\n37.7696889,\\n37.7697153,\\n37.7701244,\\n37.7700604,\\n37.7705491,\\n37.7715705,\\n37.771752,\\n37.7707533,\\n37.769866];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lats[i];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.9015113051937396,\"funcBody\":\"var lons = [-122.4261215,\\n-122.4219157,\\n-122.4199623,\\n-122.4179074,\\n-122.4155876,\\n-122.4155521,\\n-122.4163203,\\n-122.4193876,\\n-122.4210496,\\n-122.422284,\\n-122.4232717,\\n-122.4235138,\\n-122.4247605,\\n-122.4258812];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lons[i];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7253460349565717,\"funcBody\":\"var value = prevValue;\\nif (time % 500 < 100) {\\n value = value + Math.random() * 40 - 20;\\n if (value < 45) {\\n \\tvalue = 45;\\n } else if (value > 130) {\\n \\tvalue = 130;\\n }\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
Speed: ${Speed} MPH
See advanced settings for details\",\"markerImageSize\":34,\"gmDefaultMapType\":\"roadmap\",\"gmApiKey\":\"AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q\",\"useColorFunction\":true,\"markerImages\":[\"data:image/png;base64,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\",\"data:image/png;base64,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\",\"data:image/png;base64,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\"],\"useMarkerImageFunction\":true,\"colorFunction\":\"var speed = dsData[dsIndex]['Speed'];\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n if (percent < 0.5) {\\n percent *=2*100; \\n return tinycolor.mix('green', 'yellow', amount = percent).toHexString();\\n } else {\\n percent = (percent - 0.5)*2*100;\\n return tinycolor.mix('yellow', 'red', amount = percent).toHexString();\\n }\\n}\",\"markerImageFunction\":\"var speed = dsData[dsIndex]['Speed'];\\nvar res = {\\n url: images[0],\\n size: 55\\n};\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n var index = Math.min(2, Math.floor(3 * percent));\\n res.url = images[index];\\n}\\nreturn res;\",\"strokeWeight\":4,\"strokeOpacity\":0.65,\"color\":\"#1976d2\",\"showTooltip\":true,\"autocloseTooltip\":true,\"labelFunction\":\"var vehicleType = dsData[dsIndex]['vehicleType'];\\r\\nif (typeof vehicleType !== undefined) {\\r\\n if (vehicleType == \\\"bus\\\") {\\r\\n return 'Bus: ${entityName}';\\r\\n } else if (vehicleType == \\\"car\\\") {\\r\\n return 'Car: ${entityName}';\\r\\n }\\r\\n}\",\"tooltipFunction\":\"var vehicleType = dsData[dsIndex]['vehicleType'];\\r\\nif (typeof vehicleType !== undefined) {\\r\\n if (vehicleType == \\\"bus\\\") {\\r\\n return 'Bus: ${entityName}
Bus route: ${busRoute}
';\\r\\n } else if (vehicleType == \\\"car\\\") {\\r\\n return 'Car: ${entityName}
Current destination: ${destination}
';\\r\\n }\\r\\n}\",\"provider\":\"google-map\",\"defaultCenterPosition\":\"0,0\",\"showTooltipAction\":\"click\",\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"draggableMarker\":false,\"disableScrollZooming\":false,\"disableZoomControl\":false,\"useLabelFunction\":false,\"useTooltipFunction\":false,\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"showPolygon\":false,\"showCircle\":false},\"title\":\"Route Map\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" @@ -72,7 +72,7 @@ "resources": [], "templateHtml": "", "templateCss": ".leaflet-zoom-box {\n\tz-index: 9;\n}\n\n.leaflet-pane { z-index: 4; }\n\n.leaflet-tile-pane { z-index: 2; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane { z-index: 5; }\n.leaflet-marker-pane { z-index: 6; }\n.leaflet-tooltip-pane { z-index: 7; }\n.leaflet-popup-pane { z-index: 8; }\n\n.leaflet-map-pane canvas { z-index: 1; }\n.leaflet-map-pane svg { z-index: 2; }\n\n.leaflet-control {\n\tz-index: 9;\n}\n.leaflet-top,\n.leaflet-bottom {\n\tz-index: 11;\n}\n\n.tb-marker-label {\n border: none;\n background: none;\n box-shadow: none;\n}\n\n.tb-marker-label:before {\n border: none;\n background: none;\n}\n", - "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('openstreet-map', true, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('openstreet-map', true);\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('openstreet-map', true);\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true\n };\n}", + "controllerScript": "self.onInit = function() {\n self.ctx.map = new TbMapWidgetV2('openstreet-map', true, self.ctx);\n}\n\nself.onDataUpdated = function() {\n self.ctx.map.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.map.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.map.resize();\n}\n\nself.getSettingsSchema = function() {\n return TbMapWidgetV2.settingsSchema('openstreet-map', true);\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbMapWidgetV2.dataKeySettingsSchema('openstreet-map', true);\n}\n\nself.actionSources = function() {\n return TbMapWidgetV2.actionSources();\n}\n\nself.onDestroy = function() {\n self.ctx.map.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasDataPageLink: true,\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true\n };\n}", "settingsSchema": "{}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"First route\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"latitude\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.5851719234007373,\"funcBody\":\"var lats = [37.7696499,\\n37.7699074,\\n37.7699536,\\n37.7697242,\\n37.7695189,\\n37.7696889,\\n37.7697153,\\n37.7701244,\\n37.7700604,\\n37.7705491,\\n37.7715705,\\n37.771752,\\n37.7707533,\\n37.769866];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lats[i];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"longitude\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.9015113051937396,\"funcBody\":\"var lons = [-122.4261215,\\n-122.4219157,\\n-122.4199623,\\n-122.4179074,\\n-122.4155876,\\n-122.4155521,\\n-122.4163203,\\n-122.4193876,\\n-122.4210496,\\n-122.422284,\\n-122.4232717,\\n-122.4235138,\\n-122.4247605,\\n-122.4258812];\\n\\nvar i = Math.floor((time/3 % 14000) / 1000);\\n\\nreturn lons[i];\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Speed\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.7253460349565717,\"funcBody\":\"var value = prevValue;\\nif (time % 500 < 100) {\\n value = value + Math.random() * 40 - 20;\\n if (value < 45) {\\n \\tvalue = 45;\\n } else if (value > 130) {\\n \\tvalue = 130;\\n }\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"fitMapBounds\":true,\"latKeyName\":\"latitude\",\"lngKeyName\":\"longitude\",\"showLabel\":true,\"label\":\"${entityName}\",\"tooltipPattern\":\"${entityName}

Latitude: ${latitude:7}
Longitude: ${longitude:7}
Speed: ${Speed} MPH
See advanced settings for details\",\"markerImageSize\":34,\"useColorFunction\":true,\"markerImages\":[\"data:image/png;base64,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\",\"data:image/png;base64,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\",\"data:image/png;base64,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\"],\"useMarkerImageFunction\":true,\"colorFunction\":\"var speed = dsData[dsIndex]['Speed'];\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n if (percent < 0.5) {\\n percent *=2*100; \\n return tinycolor.mix('green', 'yellow', amount = percent).toHexString();\\n } else {\\n percent = (percent - 0.5)*2*100;\\n return tinycolor.mix('yellow', 'red', amount = percent).toHexString();\\n }\\n}\",\"markerImageFunction\":\"var speed = dsData[dsIndex]['Speed'];\\nvar res = {\\n url: images[0],\\n size: 55\\n};\\nif (typeof speed !== undefined) {\\n var percent = (speed - 45)/85;\\n var index = Math.min(2, Math.floor(3 * percent));\\n res.url = images[index];\\n}\\nreturn res;\",\"strokeWeight\":4,\"strokeOpacity\":0.65,\"color\":\"#1976d3\",\"mapProvider\":\"OpenStreetMap.Mapnik\",\"showTooltip\":true,\"autocloseTooltip\":true,\"labelFunction\":\"var vehicleType = dsData[dsIndex]['vehicleType'];\\r\\nif (typeof vehicleType !== undefined) {\\r\\n if (vehicleType == \\\"bus\\\") {\\r\\n return 'Bus: ${entityName}';\\r\\n } else if (vehicleType == \\\"car\\\") {\\r\\n return 'Car: ${entityName}';\\r\\n }\\r\\n}\",\"tooltipFunction\":\"var vehicleType = dsData[dsIndex]['vehicleType'];\\r\\nif (typeof vehicleType !== undefined) {\\r\\n if (vehicleType == \\\"bus\\\") {\\r\\n return 'Bus: ${entityName}
Bus route: ${busRoute}
';\\r\\n } else if (vehicleType == \\\"car\\\") {\\r\\n return 'Car: ${entityName}
Current destination: ${destination}
';\\r\\n }\\r\\n}\",\"provider\":\"openstreet-map\",\"defaultCenterPosition\":\"0,0\",\"showTooltipAction\":\"click\",\"useCustomProvider\":false,\"useDefaultCenterPosition\":false,\"mapPageSize\":16384,\"draggableMarker\":false,\"disableScrollZooming\":false,\"disableZoomControl\":false,\"useLabelFunction\":false,\"useTooltipFunction\":false,\"tooltipOffsetX\":0,\"tooltipOffsetY\":-1,\"markerOffsetX\":0.5,\"markerOffsetY\":1,\"showPolygon\":false,\"showCircle\":false,\"polygonKeyName\":\"perimeter\",\"editablePolygon\":false,\"showPolygonLabel\":false,\"usePolygonColorFunction\":false,\"polygonOpacity\":0.2,\"usePolygonStrokeColorFunction\":false,\"polygonStrokeOpacity\":1,\"polygonStrokeWeight\":3,\"showPolygonTooltip\":false},\"title\":\"Route Map - OpenStreetMap\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" diff --git a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json index a065279cce..88f8232328 100644 --- a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json @@ -156,6 +156,21 @@ "toIndex": 7, "type": "Attributes Updated" }, + { + "fromIndex": 3, + "toIndex": 7, + "type": "Attributes Deleted" + }, + { + "fromIndex": 3, + "toIndex": 7, + "type": "Timeseries Deleted" + }, + { + "fromIndex": 3, + "toIndex": 7, + "type": "Timeseries Updated" + }, { "fromIndex": 4, "toIndex": 7, diff --git a/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql b/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql new file mode 100644 index 0000000000..432fca8786 --- /dev/null +++ b/application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql @@ -0,0 +1,50 @@ +-- +-- Copyright © 2016-2022 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. +-- + + +DROP PROCEDURE IF EXISTS public.cleanup_events_by_ttl(bigint, bigint, bigint); + +CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl( + IN regular_events_start_ts bigint, + IN regular_events_end_ts bigint, + IN debug_events_start_ts bigint, + IN debug_events_end_ts bigint, + INOUT deleted bigint) + LANGUAGE plpgsql AS +$$ +DECLARE + ttl_deleted_count bigint DEFAULT 0; + debug_ttl_deleted_count bigint DEFAULT 0; +BEGIN + IF regular_events_start_ts > 0 AND regular_events_end_ts > 0 THEN + EXECUTE format( + 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || + '(event_type != %L::varchar AND event_type != %L::varchar)) RETURNING *) ' || + 'SELECT count(*) FROM deleted', regular_events_start_ts, regular_events_end_ts, + 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count; + END IF; + IF debug_events_start_ts > 0 AND debug_events_end_ts > 0 THEN + EXECUTE format( + 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || + '(event_type = %L::varchar OR event_type = %L::varchar)) RETURNING *) ' || + 'SELECT count(*) FROM deleted', debug_events_start_ts, debug_events_end_ts, + 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count; + END IF; + RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count; + RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count; + deleted := ttl_deleted_count + debug_ttl_deleted_count; +END +$$; diff --git a/application/src/main/data/upgrade/3.3.3/schema_update.sql b/application/src/main/data/upgrade/3.3.3/schema_update.sql new file mode 100644 index 0000000000..eed1cab1ea --- /dev/null +++ b/application/src/main/data/upgrade/3.3.3/schema_update.sql @@ -0,0 +1,29 @@ +-- +-- Copyright © 2016-2022 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. +-- + +DELETE from ota_package as op WHERE NOT EXISTS(SELECT * FROM device_profile dp where op.device_profile_id = dp.id); + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_device_profile_ota_package') THEN + ALTER TABLE ota_package + ADD CONSTRAINT fk_device_profile_ota_package + FOREIGN KEY (device_profile_id) REFERENCES device_profile (id) + ON DELETE CASCADE; + END IF; + END; +$$; 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 bd78631aa7..4059c51a57 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -474,7 +474,7 @@ public class ActorSystemContext { } private void persistEvent(Event event) { - eventService.save(event); + eventService.saveAsync(event); } private String toString(Throwable e) { @@ -559,10 +559,10 @@ public class ActorSystemContext { } event.setBody(node); - ListenableFuture future = eventService.saveAsync(event); - Futures.addCallback(future, new FutureCallback() { + ListenableFuture future = eventService.saveAsync(event); + Futures.addCallback(future, new FutureCallback() { @Override - public void onSuccess(@Nullable Event event) { + public void onSuccess(@Nullable Void event) { } @@ -612,10 +612,10 @@ public class ActorSystemContext { } event.setBody(node); - ListenableFuture future = eventService.saveAsync(event); - Futures.addCallback(future, new FutureCallback() { + ListenableFuture future = eventService.saveAsync(event); + Futures.addCallback(future, new FutureCallback() { @Override - public void onSuccess(@Nullable Event event) { + public void onSuccess(@Nullable Void event) { } 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 95125b331a..0b28aca7b0 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 @@ -35,6 +35,7 @@ import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -97,7 +98,6 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -107,6 +107,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -200,8 +201,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { boolean sent = false; if (systemContext.isEdgesEnabled() && edgeId != null) { log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId()); - saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()); - sent = true; + try { + saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()).get(); + sent = true; + } catch (InterruptedException | ExecutionException e) { + String errMsg = String.format("[%s][%s][%s] Failed to save rpc request to edge queue %s", tenantId, deviceId, edgeId.getId(), request); + log.error(errMsg, e); + } } else if (isSendNewRpcAvailable()) { sent = rpcSubscriptions.size() > 0; Set syncSessionSet = new HashSet<>(); @@ -810,13 +816,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { systemContext.getTbCoreToTransportService().process(nodeId, msg); } - private void saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) { - EdgeEvent edgeEvent = new EdgeEvent(); - edgeEvent.setTenantId(tenantId); - edgeEvent.setAction(EdgeEventActionType.RPC_CALL); - edgeEvent.setEntityId(deviceId.getId()); - edgeEvent.setType(EdgeEventType.DEVICE); - + private ListenableFuture saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) { ObjectNode body = mapper.createObjectNode(); body.put("requestId", requestId); body.put("requestUUID", msg.getId().toString()); @@ -824,11 +824,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { body.put("expirationTime", msg.getExpirationTime()); body.put("method", msg.getBody().getMethod()); body.put("params", msg.getBody().getParams()); - edgeEvent.setBody(body); - edgeEvent.setEdgeId(edgeId); - systemContext.getEdgeEventService().save(edgeEvent); - systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); + EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.DEVICE, EdgeEventActionType.RPC_CALL, deviceId, body); + + return Futures.transform(systemContext.getEdgeEventService().saveAsync(edgeEvent), unused -> { + systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); + return null; + }, systemContext.getDbCallbackExecutor()); } private List toTsKvProtos(@Nullable List result) { diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java index b547741453..8cdf5cec02 100644 --- a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java @@ -59,7 +59,7 @@ public class StatsActor extends ContextAwareActor { event.setTenantId(msg.getTenantId()); event.setType(DataConstants.STATS); event.setBody(toBodyJson(systemContext.getServiceInfoProvider().getServiceId(), msg.getMessagesProcessed(), msg.getErrorsOccurred())); - systemContext.getEventService().save(event); + systemContext.getEventService().saveAsync(event); } private JsonNode toBodyJson(String serviceId, long messagesProcessed, long errorsOccurred) { 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 874d91ff81..c22b44d7cf 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 @@ -32,6 +32,7 @@ import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; @@ -60,7 +61,7 @@ import java.util.List; @Slf4j public class TenantActor extends RuleChainManagerActor { - private boolean isRuleEngineForCurrentTenant; + private boolean isRuleEngine; private boolean isCore; private ApiUsageState apiUsageState; @@ -73,21 +74,21 @@ public class TenantActor extends RuleChainManagerActor { @Override public void init(TbActorCtx ctx) throws TbActorException { super.init(ctx); - log.info("[{}] Starting tenant actor.", tenantId); + log.debug("[{}] Starting tenant actor.", tenantId); try { Tenant tenant = systemContext.getTenantService().findTenantById(tenantId); if (tenant == null) { cantFindTenant = true; log.info("[{}] Started tenant actor for missing tenant.", tenantId); } else { - apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenant.getId())); + TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); - isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); - if (isRuleEngineForCurrentTenant) { + isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); + if (isRuleEngine) { try { - if (apiUsageState.isReExecEnabled()) { - log.info("[{}] Going to init rule chains", tenantId); + if (getApiUsageState().isReExecEnabled()) { + log.debug("[{}] Going to init rule chains", tenantId); initRuleChains(); } else { log.info("[{}] Skip init of the rule chains due to API limits", tenantId); @@ -96,7 +97,7 @@ public class TenantActor extends RuleChainManagerActor { cantFindTenant = true; } } - log.info("[{}] Tenant actor started.", tenantId); + log.debug("[{}] Tenant actor started.", tenantId); } } catch (Exception e) { log.warn("[{}] Unknown failure", tenantId, e); @@ -179,12 +180,12 @@ public class TenantActor extends RuleChainManagerActor { } private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) { - if (!isRuleEngineForCurrentTenant) { + if (!isRuleEngine) { log.warn("RECEIVED INVALID MESSAGE: {}", msg); return; } TbMsg tbMsg = msg.getMsg(); - if (apiUsageState.isReExecEnabled()) { + if (getApiUsageState().isReExecEnabled()) { if (tbMsg.getRuleChainId() == null) { if (getRootChainActor() != null) { getRootChainActor().tell(msg); @@ -208,7 +209,7 @@ public class TenantActor extends RuleChainManagerActor { } private void onRuleChainMsg(RuleChainAwareMsg msg) { - if (apiUsageState.isReExecEnabled()) { + if (getApiUsageState().isReExecEnabled()) { getOrCreateActor(msg.getRuleChainId()).tell(msg); } } @@ -227,7 +228,7 @@ public class TenantActor extends RuleChainManagerActor { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { - ApiUsageState old = apiUsageState; + ApiUsageState old = getApiUsageState(); apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId)); if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) { log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId); @@ -247,7 +248,7 @@ public class TenantActor extends RuleChainManagerActor { edgeRpcService.updateEdge(tenantId, edge); } } - } else if (isRuleEngineForCurrentTenant) { + } else if (isRuleEngine) { TbActorRef target = getEntityActorRef(msg.getEntityId()); if (target != null) { if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { @@ -275,6 +276,13 @@ public class TenantActor extends RuleChainManagerActor { systemContext.getEdgeRpcService().onEdgeEvent(tenantId, msg.getEdgeId()); } + private ApiUsageState getApiUsageState() { + if (apiUsageState == null) { + apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId)); + } + return apiUsageState; + } + public static class ActorCreator extends ContextBasedCreator { private final TenantId tenantId; diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index e71b881477..b6ba0088bb 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.core.annotation.Order; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; @@ -84,6 +85,7 @@ import static springfox.documentation.builders.PathSelectors.regex; @Slf4j @Configuration @TbCoreComponent +@Profile("!test") public class SwaggerConfiguration { @Value("${swagger.api_path_regex}") diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 90703481cd..3e9597a72d 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -176,16 +176,16 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt return new BCryptPasswordEncoder(); } - @Override - public void configure(WebSecurity web) throws Exception { - web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**"); - } - @Autowired private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver; @Override protected void configure(HttpSecurity http) throws Exception { + http.authorizeHttpRequests((authorizeHttpRequests) -> + authorizeHttpRequests + .antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**") + .permitAll() + ); http.headers().cacheControl().and().frameOptions().disable() .and() .cors() diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java index 664e6c3181..13b13123e6 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java @@ -55,7 +55,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { - registry.addHandler(wsHandler(), WS_PLUGIN_MAPPING).setAllowedOrigins("*") + registry.addHandler(wsHandler(), WS_PLUGIN_MAPPING).setAllowedOriginPatterns("*") .addInterceptors(new HttpSessionHandshakeInterceptor(), new HandshakeInterceptor() { @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index 7bd9cb065d..4718feb774 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -34,13 +34,10 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -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.AssetId; @@ -54,6 +51,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.asset.AssetBulkImportService; +import org.thingsboard.server.service.entitiy.asset.TbAssetService; import org.thingsboard.server.service.importing.BulkImportRequest; import org.thingsboard.server.service.importing.BulkImportResult; import org.thingsboard.server.service.security.model.SecurityUser; @@ -95,6 +93,7 @@ import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE @Slf4j public class AssetController extends BaseController { private final AssetBulkImportService assetBulkImportService; + private final TbAssetService tbAssetService; public static final String ASSET_ID = "assetId"; @@ -145,39 +144,12 @@ public class AssetController extends BaseController { @RequestMapping(value = "/asset", method = RequestMethod.POST) @ResponseBody public Asset saveAsset(@ApiParam(value = "A JSON value representing the asset.") @RequestBody Asset asset) throws ThingsboardException { - try { - if (TB_SERVICE_QUEUE.equals(asset.getType())) { - throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - - asset.setTenantId(getCurrentUser().getTenantId()); - - checkEntity(asset.getId(), asset, Resource.ASSET); - - Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); - - onAssetCreatedOrUpdated(savedAsset, asset.getId() != null, getCurrentUser()); - - return savedAsset; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.ASSET), asset, - null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); - throw handleException(e); - } - } - - private void onAssetCreatedOrUpdated(Asset asset, boolean updated, SecurityUser user) { - try { - logEntityAction(user, asset.getId(), asset, - asset.getCustomerId(), - updated ? ActionType.UPDATED : ActionType.ADDED, null); - } catch (ThingsboardException e) { - log.error("Failed to log entity action", e); - } - - if (updated) { - sendEntityNotificationMsg(asset.getTenantId(), asset.getId(), EdgeEventActionType.UPDATED); + if (TB_SERVICE_QUEUE.equals(asset.getType())) { + throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); } + asset.setTenantId(getCurrentUser().getTenantId()); + checkEntity(asset.getId(), asset, Resource.ASSET); + return tbAssetService.save(asset, getCurrentUser()); } @ApiOperation(value = "Delete asset (deleteAsset)", @@ -190,21 +162,8 @@ public class AssetController extends BaseController { try { AssetId assetId = new AssetId(toUUID(strAssetId)); Asset asset = checkAssetId(assetId, Operation.DELETE); - - List relatedEdgeIds = findRelatedEdgeIds(getTenantId(), assetId); - - assetService.deleteAsset(getTenantId(), assetId); - - logEntityAction(assetId, asset, - asset.getCustomerId(), - ActionType.DELETED, null, strAssetId); - - sendDeleteNotificationMsg(getTenantId(), assetId, relatedEdgeIds); + tbAssetService.delete(asset, getCurrentUser()).get(); } catch (Exception e) { - logEntityAction(emptyId(EntityType.ASSET), - null, - null, - ActionType.DELETED, e, strAssetId); throw handleException(e); } } @@ -218,31 +177,11 @@ public class AssetController extends BaseController { @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter("customerId", strCustomerId); checkParameter(ASSET_ID, strAssetId); - try { - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId, Operation.READ); - - AssetId assetId = new AssetId(toUUID(strAssetId)); - checkAssetId(assetId, Operation.ASSIGN_TO_CUSTOMER); - - Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(getTenantId(), assetId, customerId)); - - logEntityAction(assetId, savedAsset, - savedAsset.getCustomerId(), - ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName()); - - sendEntityAssignToCustomerNotificationMsg(savedAsset.getTenantId(), savedAsset.getId(), - customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER); - - return savedAsset; - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.ASSET), null, - null, - ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId, strCustomerId); - - throw handleException(e); - } + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + Customer customer = checkCustomerId(customerId, Operation.READ); + AssetId assetId = new AssetId(toUUID(strAssetId)); + checkAssetId(assetId, Operation.ASSIGN_TO_CUSTOMER); + return tbAssetService.assignAssetToCustomer(getTenantId(), assetId, customer, getCurrentUser()); } @ApiOperation(value = "Unassign asset from customer (unassignAssetFromCustomer)", @@ -252,33 +191,13 @@ public class AssetController extends BaseController { @ResponseBody public Asset unassignAssetFromCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); - try { - AssetId assetId = new AssetId(toUUID(strAssetId)); - Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_CUSTOMER); - if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - throw new IncorrectParameterException("Asset isn't assigned to any customer!"); - } - - Customer customer = checkCustomerId(asset.getCustomerId(), Operation.READ); - - Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(getTenantId(), assetId)); - - logEntityAction(assetId, asset, - asset.getCustomerId(), - ActionType.UNASSIGNED_FROM_CUSTOMER, null, strAssetId, customer.getId().toString(), customer.getName()); - - sendEntityAssignToCustomerNotificationMsg(savedAsset.getTenantId(), savedAsset.getId(), - customer.getId(), EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER); - - return savedAsset; - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.ASSET), null, - null, - ActionType.UNASSIGNED_FROM_CUSTOMER, e, strAssetId); - - throw handleException(e); + AssetId assetId = new AssetId(toUUID(strAssetId)); + Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_CUSTOMER); + if (asset.getCustomerId() == null || asset.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { + throw new IncorrectParameterException("Asset isn't assigned to any customer!"); } + Customer customer = checkCustomerId(asset.getCustomerId(), Operation.READ); + return tbAssetService.unassignAssetToCustomer(getTenantId(), assetId, customer, getCurrentUser()); } @ApiOperation(value = "Make asset publicly available (assignAssetToPublicCustomer)", @@ -290,25 +209,9 @@ public class AssetController extends BaseController { @ResponseBody public Asset assignAssetToPublicCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(ASSET_ID, strAssetId); - try { - AssetId assetId = new AssetId(toUUID(strAssetId)); - Asset asset = checkAssetId(assetId, Operation.ASSIGN_TO_CUSTOMER); - Customer publicCustomer = customerService.findOrCreatePublicCustomer(asset.getTenantId()); - Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(getTenantId(), assetId, publicCustomer.getId())); - - logEntityAction(assetId, savedAsset, - savedAsset.getCustomerId(), - ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, publicCustomer.getId().toString(), publicCustomer.getName()); - - return savedAsset; - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.ASSET), null, - null, - ActionType.ASSIGNED_TO_CUSTOMER, e, strAssetId); - - throw handleException(e); - } + AssetId assetId = new AssetId(toUUID(strAssetId)); + checkAssetId(assetId, Operation.ASSIGN_TO_CUSTOMER); + return tbAssetService.assignAssetToPublicCustomer(getTenantId(), assetId, getCurrentUser()); } @ApiOperation(value = "Get Tenant Assets (getTenantAssets)", @@ -553,30 +456,14 @@ public class AssetController extends BaseController { @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(ASSET_ID, strAssetId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.READ); - AssetId assetId = new AssetId(toUUID(strAssetId)); - checkAssetId(assetId, Operation.READ); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.READ); - Asset savedAsset = checkNotNull(assetService.assignAssetToEdge(getTenantId(), assetId, edgeId)); + AssetId assetId = new AssetId(toUUID(strAssetId)); + checkAssetId(assetId, Operation.READ); - logEntityAction(assetId, savedAsset, - savedAsset.getCustomerId(), - ActionType.ASSIGNED_TO_EDGE, null, strAssetId, strEdgeId, edge.getName()); - - sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedAsset.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE); - - return savedAsset; - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.ASSET), null, - null, - ActionType.ASSIGNED_TO_EDGE, e, strAssetId, strEdgeId); - - throw handleException(e); - } + return tbAssetService.assignAssetToEdge(getTenantId(), assetId, edge, getCurrentUser()); } @ApiOperation(value = "Unassign asset from edge (unassignAssetFromEdge)", @@ -593,30 +480,13 @@ public class AssetController extends BaseController { @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(ASSET_ID, strAssetId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.READ); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.READ); - AssetId assetId = new AssetId(toUUID(strAssetId)); - Asset asset = checkAssetId(assetId, Operation.READ); - - Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(getTenantId(), assetId, edgeId)); - - logEntityAction(assetId, asset, - asset.getCustomerId(), - ActionType.UNASSIGNED_FROM_EDGE, null, strAssetId, strEdgeId, edge.getName()); + AssetId assetId = new AssetId(toUUID(strAssetId)); + Asset asset = checkAssetId(assetId, Operation.READ); - sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedAsset.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE); - - return savedAsset; - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.ASSET), null, - null, - ActionType.UNASSIGNED_FROM_EDGE, e, strAssetId, strEdgeId); - - throw handleException(e); - } + return tbAssetService.unassignAssetFromEdge(getTenantId(), asset, edge, getCurrentUser()); } @ApiOperation(value = "Get assets assigned to edge (getEdgeAssets)", @@ -680,9 +550,7 @@ public class AssetController extends BaseController { @PostMapping("/asset/bulk_import") public BulkImportResult processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception { SecurityUser user = getCurrentUser(); - return assetBulkImportService.processBulkImport(request, user, importedAssetInfo -> { - onAssetCreatedOrUpdated(importedAssetInfo.getEntity(), importedAssetInfo.isUpdated(), user); - }); + return assetBulkImportService.processBulkImport(request, user); } } 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 1dafef871a..b787a85d1b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -355,8 +355,12 @@ public abstract class BaseController { } } - UUID toUUID(String id) { - return UUID.fromString(id); + UUID toUUID(String id) throws ThingsboardException { + try { + return UUID.fromString(id); + } catch (IllegalArgumentException e) { + throw handleException(e, false); + } } PageLink createPageLink(int pageSize, int page, String textSearch, String sortProperty, String sortOrder) throws ThingsboardException { 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 85a21e912c..119522a03c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -37,20 +37,16 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; 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.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.edge.Edge; -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.CustomerId; @@ -63,9 +59,6 @@ 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.security.DeviceCredentials; -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.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult; @@ -73,7 +66,7 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.device.DeviceBulkImportService; -import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; +import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.importing.BulkImportRequest; import org.thingsboard.server.service.importing.BulkImportResult; import org.thingsboard.server.service.security.model.SecurityUser; @@ -81,7 +74,6 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import javax.annotation.Nullable; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -128,7 +120,7 @@ public class DeviceController extends BaseController { private final DeviceBulkImportService deviceBulkImportService; - private final GatewayNotificationsService gatewayNotificationsService; + private final TbDeviceService tbDeviceService; @ApiOperation(value = "Get Device (getDeviceById)", notes = "Fetch the Device object based on the provided Device Id. " + @@ -141,12 +133,8 @@ public class DeviceController extends BaseController { public Device getDeviceById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); - try { - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - return checkDeviceId(deviceId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + return checkDeviceId(deviceId, Operation.READ); } @ApiOperation(value = "Get Device Info (getDeviceInfoById)", @@ -160,12 +148,8 @@ public class DeviceController extends BaseController { public DeviceInfo getDeviceInfoById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); - try { - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - return checkDeviceInfoId(deviceId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + return checkDeviceInfoId(deviceId, Operation.READ); } @ApiOperation(value = "Create Or Update Device (saveDevice)", @@ -182,27 +166,14 @@ public class DeviceController extends BaseController { public Device saveDevice(@ApiParam(value = "A JSON value representing the device.") @RequestBody Device device, @ApiParam(value = "Optional value of the device credentials to be used during device creation. " + "If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { - boolean created = device.getId() == null; - try { - device.setTenantId(getCurrentUser().getTenantId()); - - Device oldDevice = null; - if (!created) { - oldDevice = checkDeviceId(device.getId(), Operation.WRITE); - } else { - checkEntity(null, device, Resource.DEVICE); - } - - Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); - - onDeviceCreatedOrUpdated(savedDevice, oldDevice, !created, getCurrentUser()); - - return savedDevice; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), device, - null, created ? ActionType.ADDED : ActionType.UPDATED, e); - throw handleException(e); + device.setTenantId(getCurrentUser().getTenantId()); + Device oldDevice = null; + if (device.getId() != null) { + oldDevice = checkDeviceId(device.getId(), Operation.WRITE); + } else { + checkEntity(null, device, Resource.DEVICE); } + return tbDeviceService.save(getTenantId(), device, oldDevice, accessToken, getCurrentUser()); } @ApiOperation(value = "Create Device (saveDevice) with credentials ", @@ -218,36 +189,11 @@ public class DeviceController extends BaseController { @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException { Device device = checkNotNull(deviceAndCredentials.getDevice()); DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials()); - boolean created = device.getId() == null; - try { - device.setTenantId(getCurrentUser().getTenantId()); - checkEntity(device.getId(), device, Resource.DEVICE); - Device savedDevice = deviceService.saveDeviceWithCredentials(device, credentials); - checkNotNull(savedDevice); - tbClusterService.onDeviceUpdated(savedDevice, device); - logEntityAction(savedDevice.getId(), savedDevice, - savedDevice.getCustomerId(), - device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); - - return savedDevice; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), device, - null, created ? ActionType.ADDED : ActionType.UPDATED, e); - throw handleException(e); - } + device.setTenantId(getCurrentUser().getTenantId()); + checkEntity(device.getId(), device, Resource.DEVICE); + return tbDeviceService.saveDeviceWithCredentials(getTenantId(), device, credentials, getCurrentUser()); } - private void onDeviceCreatedOrUpdated(Device savedDevice, Device oldDevice, boolean updated, SecurityUser user) { - tbClusterService.onDeviceUpdated(savedDevice, oldDevice); - - try { - logEntityAction(user, savedDevice.getId(), savedDevice, - savedDevice.getCustomerId(), - updated ? ActionType.UPDATED : ActionType.ADDED, null); - } catch (ThingsboardException e) { - log.error("Failed to log entity action", e); - } - } @ApiOperation(value = "Delete device (deleteDevice)", notes = "Deletes the device, it's credentials and all the relations (from and to the device). Referencing non-existing device Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) @@ -257,27 +203,11 @@ public class DeviceController extends BaseController { public void deleteDevice(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.DELETE); try { - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.DELETE); - - List relatedEdgeIds = findRelatedEdgeIds(getTenantId(), deviceId); - - deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId); - - gatewayNotificationsService.onDeviceDeleted(device); - tbClusterService.onDeviceDeleted(device, null); - - logEntityAction(deviceId, device, - device.getCustomerId(), - ActionType.DELETED, null, strDeviceId); - - sendDeleteNotificationMsg(getTenantId(), deviceId, relatedEdgeIds); + tbDeviceService.deleteDevice(device, getCurrentUser()).get(); } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), - null, - null, - ActionType.DELETED, e, strDeviceId); throw handleException(e); } } @@ -293,29 +223,11 @@ public class DeviceController extends BaseController { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter("customerId", strCustomerId); checkParameter(DEVICE_ID, strDeviceId); - try { - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId, Operation.READ); - - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - checkDeviceId(deviceId, Operation.ASSIGN_TO_CUSTOMER); - - Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(getCurrentUser().getTenantId(), deviceId, customerId)); - - logEntityAction(deviceId, savedDevice, - savedDevice.getCustomerId(), - ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, strCustomerId, customer.getName()); - - sendEntityAssignToCustomerNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(), - customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER); - - return savedDevice; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), null, - null, - ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId, strCustomerId); - throw handleException(e); - } + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + Customer customer = checkCustomerId(customerId, Operation.READ); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + checkDeviceId(deviceId, Operation.ASSIGN_TO_CUSTOMER); + return tbDeviceService.assignDeviceToCustomer(getTenantId(), deviceId, customer, getCurrentUser()); } @ApiOperation(value = "Unassign device from customer (unassignDeviceFromCustomer)", @@ -332,22 +244,11 @@ public class DeviceController extends BaseController { if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { throw new IncorrectParameterException("Device isn't assigned to any customer!"); } - Customer customer = checkCustomerId(device.getCustomerId(), Operation.READ); - - Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(getCurrentUser().getTenantId(), deviceId)); - - logEntityAction(deviceId, device, - device.getCustomerId(), - ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDeviceId, customer.getId().toString(), customer.getName()); - sendEntityAssignToCustomerNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(), - customer.getId(), EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER); + Customer customer = checkCustomerId(device.getCustomerId(), Operation.READ); - return savedDevice; + return tbDeviceService.unassignDeviceFromCustomer(device, customer, getCurrentUser()); } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), null, - null, - ActionType.UNASSIGNED_FROM_CUSTOMER, e, strDeviceId); throw handleException(e); } } @@ -362,23 +263,9 @@ public class DeviceController extends BaseController { public Device assignDeviceToPublicCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); - try { - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_CUSTOMER); - Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId()); - Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(getCurrentUser().getTenantId(), deviceId, publicCustomer.getId())); - - logEntityAction(deviceId, savedDevice, - savedDevice.getCustomerId(), - ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, publicCustomer.getId().toString(), publicCustomer.getName()); - - return savedDevice; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), null, - null, - ActionType.ASSIGNED_TO_CUSTOMER, e, strDeviceId); - throw handleException(e); - } + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + checkDeviceId(deviceId, Operation.ASSIGN_TO_CUSTOMER); + return tbDeviceService.assignDeviceToPublicCustomer(getTenantId(), deviceId, getCurrentUser()); } @ApiOperation(value = "Get Device Credentials (getDeviceCredentialsByDeviceId)", @@ -389,20 +276,9 @@ public class DeviceController extends BaseController { public DeviceCredentials getDeviceCredentialsByDeviceId(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); - try { - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); - DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(getCurrentUser().getTenantId(), deviceId)); - logEntityAction(deviceId, device, - device.getCustomerId(), - ActionType.CREDENTIALS_READ, null, strDeviceId); - return deviceCredentials; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), null, - null, - ActionType.CREDENTIALS_READ, e, strDeviceId); - throw handleException(e); - } + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.READ_CREDENTIALS); + return tbDeviceService.getDeviceCredentialsByDeviceId(device, getCurrentUser()); } @ApiOperation(value = "Update device credentials (updateDeviceCredentials)", notes = "During device creation, platform generates random 'ACCESS_TOKEN' credentials. " + @@ -416,23 +292,8 @@ public class DeviceController extends BaseController { @ApiParam(value = "A JSON value representing the device credentials.") @RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { checkNotNull(deviceCredentials); - try { - Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); - DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials)); - tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null); - - sendEntityNotificationMsg(getTenantId(), device.getId(), EdgeEventActionType.CREDENTIALS_UPDATED); - - logEntityAction(device.getId(), device, - device.getCustomerId(), - ActionType.CREDENTIALS_UPDATED, null, deviceCredentials); - return result; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), null, - null, - ActionType.CREDENTIALS_UPDATED, e, deviceCredentials); - throw handleException(e); - } + Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); + return tbDeviceService.updateDeviceCredentials(device, deviceCredentials, getCurrentUser()); } @ApiOperation(value = "Get Tenant Devices (getTenantDevices)", @@ -692,52 +553,42 @@ public class DeviceController extends BaseController { @ApiParam(value = "Claiming request which can optionally contain secret key") @RequestBody(required = false) ClaimRequest claimRequest) throws ThingsboardException { checkParameter(DEVICE_NAME, deviceName); - try { - final DeferredResult deferredResult = new DeferredResult<>(); - - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); - CustomerId customerId = user.getCustomerId(); - - Device device = checkNotNull(deviceService.findDeviceByTenantIdAndName(tenantId, deviceName)); - accessControlService.checkPermission(user, Resource.DEVICE, Operation.CLAIM_DEVICES, - device.getId(), device); - String secretKey = getSecretKey(claimRequest); + final DeferredResult deferredResult = new DeferredResult<>(); - ListenableFuture future = claimDevicesService.claimDevice(device, customerId, secretKey); - Futures.addCallback(future, new FutureCallback() { - @Override - public void onSuccess(@Nullable ClaimResult result) { - HttpStatus status; - if (result != null) { - if (result.getResponse().equals(ClaimResponse.SUCCESS)) { - status = HttpStatus.OK; - deferredResult.setResult(new ResponseEntity<>(result, status)); - - try { - logEntityAction(user, device.getId(), result.getDevice(), customerId, ActionType.ASSIGNED_TO_CUSTOMER, null, - device.getId().toString(), customerId.toString(), customerService.findCustomerById(tenantId, customerId).getName()); - } catch (ThingsboardException e) { - throw new RuntimeException(e); - } - } else { - status = HttpStatus.BAD_REQUEST; - deferredResult.setResult(new ResponseEntity<>(result.getResponse(), status)); - } + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); + CustomerId customerId = user.getCustomerId(); + + Device device = checkNotNull(deviceService.findDeviceByTenantIdAndName(tenantId, deviceName)); + accessControlService.checkPermission(user, Resource.DEVICE, Operation.CLAIM_DEVICES, + device.getId(), device); + String secretKey = getSecretKey(claimRequest); + + ListenableFuture future = tbDeviceService.claimDevice(tenantId, device, customerId, secretKey, user); + + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable ClaimResult result) { + HttpStatus status; + if (result != null) { + if (result.getResponse().equals(ClaimResponse.SUCCESS)) { + status = HttpStatus.OK; + deferredResult.setResult(new ResponseEntity<>(result, status)); } else { - deferredResult.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); + status = HttpStatus.BAD_REQUEST; + deferredResult.setResult(new ResponseEntity<>(result.getResponse(), status)); } + } else { + deferredResult.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); } + } - @Override - public void onFailure(Throwable t) { - deferredResult.setErrorResult(t); - } - }, MoreExecutors.directExecutor()); - return deferredResult; - } catch (Exception e) { - throw handleException(e); - } + @Override + public void onFailure(Throwable t) { + deferredResult.setErrorResult(t); + } + }, MoreExecutors.directExecutor()); + return deferredResult; } @ApiOperation(value = "Reclaim device (reClaimDevice)", @@ -759,21 +610,11 @@ public class DeviceController extends BaseController { accessControlService.checkPermission(user, Resource.DEVICE, Operation.CLAIM_DEVICES, device.getId(), device); - ListenableFuture result = claimDevicesService.reClaimDevice(tenantId, device); + ListenableFuture result = tbDeviceService.reclaimDevice(tenantId, device, user); Futures.addCallback(result, new FutureCallback<>() { @Override public void onSuccess(ReclaimResult reclaimResult) { deferredResult.setResult(new ResponseEntity(HttpStatus.OK)); - - Customer unassignedCustomer = reclaimResult.getUnassignedCustomer(); - if (unassignedCustomer != null) { - try { - logEntityAction(user, device.getId(), device, device.getCustomerId(), ActionType.UNASSIGNED_FROM_CUSTOMER, null, - device.getId().toString(), unassignedCustomer.getId().toString(), unassignedCustomer.getName()); - } catch (ThingsboardException e) { - throw new RuntimeException(e); - } - } } @Override @@ -787,7 +628,7 @@ public class DeviceController extends BaseController { } } - private String getSecretKey(ClaimRequest claimRequest) throws IOException { + private String getSecretKey(ClaimRequest claimRequest) { String secretKey = claimRequest.getSecretKey(); if (secretKey != null) { return secretKey; @@ -806,47 +647,15 @@ public class DeviceController extends BaseController { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(TENANT_ID, strTenantId); checkParameter(DEVICE_ID, strDeviceId); - try { - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.ASSIGN_TO_TENANT); - TenantId newTenantId = TenantId.fromUUID(toUUID(strTenantId)); - Tenant newTenant = tenantService.findTenantById(newTenantId); - if (newTenant == null) { - throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - - Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device); - - logEntityAction(getCurrentUser(), deviceId, assignedDevice, - assignedDevice.getCustomerId(), - ActionType.ASSIGNED_TO_TENANT, null, strTenantId, newTenant.getName()); - - Tenant currentTenant = tenantService.findTenantById(getTenantId()); - pushAssignedFromNotification(currentTenant, newTenantId, assignedDevice); - - return assignedDevice; - } catch (Exception e) { - logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null, - null, - ActionType.ASSIGNED_TO_TENANT, e, strTenantId); - throw handleException(e); - } - } - - private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { - String data = entityToStr(assignedDevice); - if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); - tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); + TenantId newTenantId = TenantId.fromUUID(toUUID(strTenantId)); + Tenant newTenant = tenantService.findTenantById(newTenantId); + if (newTenant == null) { + throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } - } - - private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) { - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString()); - metaData.putValue("assignedFromTenantName", tenant.getName()); - return metaData; + return tbDeviceService.assignDeviceToTenant(device, newTenant, getCurrentUser()); } @ApiOperation(value = "Assign device to edge (assignDeviceToEdge)", @@ -865,28 +674,13 @@ public class DeviceController extends BaseController { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(DEVICE_ID, strDeviceId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.READ); - - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - checkDeviceId(deviceId, Operation.READ); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.READ); - Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(getCurrentUser().getTenantId(), deviceId, edgeId)); + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + checkDeviceId(deviceId, Operation.READ); - logEntityAction(deviceId, savedDevice, - savedDevice.getCustomerId(), - ActionType.ASSIGNED_TO_EDGE, null, strDeviceId, strEdgeId, edge.getName()); - - sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDevice.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE); - - return savedDevice; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), null, - null, - ActionType.ASSIGNED_TO_EDGE, e, strDeviceId, strEdgeId); - throw handleException(e); - } + return tbDeviceService.assignDeviceToEdge(getTenantId(), deviceId, edge, getCurrentUser()); } @ApiOperation(value = "Unassign device from edge (unassignDeviceFromEdge)", @@ -905,28 +699,12 @@ public class DeviceController extends BaseController { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter(DEVICE_ID, strDeviceId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.READ); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.READ); - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.READ); - - Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(getCurrentUser().getTenantId(), deviceId, edgeId)); - - logEntityAction(deviceId, device, - device.getCustomerId(), - ActionType.UNASSIGNED_FROM_EDGE, null, strDeviceId, strEdgeId, edge.getName()); - - sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDevice.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE); - - return savedDevice; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.DEVICE), null, - null, - ActionType.UNASSIGNED_FROM_EDGE, e, strDeviceId, strEdgeId); - throw handleException(e); - } + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.READ); + return tbDeviceService.unassignDeviceFromEdge(device, edge, getCurrentUser()); } @ApiOperation(value = "Get devices assigned to edge (getEdgeDevices)", @@ -992,10 +770,11 @@ public class DeviceController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET) @ResponseBody - public Long countByDeviceProfileAndEmptyOtaPackage(@ApiParam(value = "OTA package type", allowableValues = "FIRMWARE, SOFTWARE") - @PathVariable("otaPackageType") String otaPackageType, - @ApiParam(value = "Device Profile Id. I.g. '784f394c-42b6-435a-983c-b7beff2784f9'") - @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { + public Long countByDeviceProfileAndEmptyOtaPackage + (@ApiParam(value = "OTA package type", allowableValues = "FIRMWARE, SOFTWARE") + @PathVariable("otaPackageType") String otaPackageType, + @ApiParam(value = "Device Profile Id. I.g. '784f394c-42b6-435a-983c-b7beff2784f9'") + @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { checkParameter("OtaPackageType", otaPackageType); checkParameter("DeviceProfileId", deviceProfileId); try { @@ -1012,11 +791,10 @@ public class DeviceController extends BaseController { notes = "There's an ability to import the bulk of devices using the only .csv file." + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PostMapping("/device/bulk_import") - public BulkImportResult processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception { + public BulkImportResult processDevicesBulkImport(@RequestBody BulkImportRequest request) throws + Exception { SecurityUser user = getCurrentUser(); - return deviceBulkImportService.processBulkImport(request, user, importedDeviceInfo -> { - onDeviceCreatedOrUpdated(importedDeviceInfo.getEntity(), importedDeviceInfo.getOldEntity(), importedDeviceInfo.isUpdated(), user); - }); + return deviceBulkImportService.processBulkImport(request, user); } } 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 5f79efaffa..8a01e9ef02 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -34,10 +34,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeInfo; import org.thingsboard.server.common.data.edge.EdgeSearchQuery; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; @@ -48,20 +45,19 @@ import org.thingsboard.server.common.data.id.RuleChainId; 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.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.EdgeBulkImportService; +import org.thingsboard.server.service.entitiy.edge.TbEdgeService; import org.thingsboard.server.service.importing.BulkImportRequest; import org.thingsboard.server.service.importing.BulkImportResult; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -90,6 +86,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI @RequiredArgsConstructor public class EdgeController extends BaseController { private final EdgeBulkImportService edgeBulkImportService; + private final TbEdgeService tbEdgeService; public static final String EDGE_ID = "edgeId"; public static final String EDGE_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the edge is owned by the same tenant. " + @@ -144,84 +141,43 @@ public class EdgeController extends BaseController { "Specify existing Edge id to update the edge. " + "Referencing non-existing Edge Id will cause 'Not Found' error." + "\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." - + TENANT_AUTHORITY_PARAGRAPH, + + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge", method = RequestMethod.POST) @ResponseBody public Edge saveEdge(@ApiParam(value = "A JSON value representing the edge.", required = true) @RequestBody Edge edge) throws ThingsboardException { - try { - TenantId tenantId = getCurrentUser().getTenantId(); - edge.setTenantId(tenantId); - boolean created = edge.getId() == null; - - RuleChain edgeTemplateRootRuleChain = null; - if (created) { - edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(tenantId); - if (edgeTemplateRootRuleChain == null) { - throw new DataValidationException("Root edge rule chain is not available!"); - } + TenantId tenantId = getCurrentUser().getTenantId(); + edge.setTenantId(tenantId); + boolean created = edge.getId() == null; + + RuleChain edgeTemplateRootRuleChain = null; + if (created) { + edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(tenantId); + if (edgeTemplateRootRuleChain == null) { + throw new DataValidationException("Root edge rule chain is not available!"); } - - Operation operation = created ? Operation.CREATE : Operation.WRITE; - - accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, - edge.getId(), edge); - - Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true)); - onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser()); - - return savedEdge; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.EDGE), edge, - null, edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); - throw handleException(e); } - } - private void onEdgeCreatedOrUpdated(TenantId tenantId, Edge edge, RuleChain edgeTemplateRootRuleChain, boolean updated, SecurityUser user) throws IOException, ThingsboardException { - if (!updated) { - ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edge.getId()); - edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, edgeTemplateRootRuleChain.getId()); - edgeService.assignDefaultRuleChainsToEdge(tenantId, edge.getId()); - } + Operation operation = created ? Operation.CREATE : Operation.WRITE; - tbClusterService.broadcastEntityStateChangeEvent(edge.getTenantId(), edge.getId(), - updated ? ComponentLifecycleEvent.UPDATED : ComponentLifecycleEvent.CREATED); + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edge.getId(), edge); - logEntityAction(user, edge.getId(), edge, null, updated ? ActionType.UPDATED : ActionType.ADDED, null); + return tbEdgeService.saveEdge(edge, edgeTemplateRootRuleChain, getCurrentUser()); } @ApiOperation(value = "Delete edge (deleteEdge)", - notes = "Deletes the edge. Referencing non-existing edge Id will cause an error."+ TENANT_AUTHORITY_PARAGRAPH) + notes = "Deletes the edge. Referencing non-existing edge Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.DELETE); - edgeService.deleteEdge(getTenantId(), edgeId); - - tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, - ComponentLifecycleEvent.DELETED); - - logEntityAction(edgeId, edge, - null, - ActionType.DELETED, null, strEdgeId); - - } catch (Exception e) { - - logEntityAction(emptyId(EntityType.EDGE), - null, - null, - ActionType.DELETED, e, strEdgeId); - - throw handleException(e); - } + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.DELETE); + tbEdgeService.deleteEdge(edge, getCurrentUser()); } @ApiOperation(value = "Get Tenant Edges (getEdges)", @@ -261,32 +217,11 @@ public class EdgeController extends BaseController { @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter("customerId", strCustomerId); checkParameter(EDGE_ID, strEdgeId); - try { - CustomerId customerId = new CustomerId(toUUID(strCustomerId)); - Customer customer = checkCustomerId(customerId, Operation.READ); - - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER); - - Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, customerId)); - - tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, - ComponentLifecycleEvent.UPDATED); - - logEntityAction(edgeId, savedEdge, - savedEdge.getCustomerId(), - ActionType.ASSIGNED_TO_CUSTOMER, null, strEdgeId, strCustomerId, customer.getName()); - - sendEntityAssignToCustomerNotificationMsg(savedEdge.getTenantId(), savedEdge.getId(), - customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER); - - return savedEdge; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.EDGE), null, - null, - ActionType.ASSIGNED_TO_CUSTOMER, e, strEdgeId, strCustomerId); - throw handleException(e); - } + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); + Customer customer = checkCustomerId(customerId, Operation.READ); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER); + return tbEdgeService.assignEdgeToCustomer(getTenantId(), edgeId, customer, getCurrentUser()); } @ApiOperation(value = "Unassign edge from customer (unassignEdgeFromCustomer)", @@ -298,33 +233,14 @@ public class EdgeController extends BaseController { public Edge unassignEdgeFromCustomer(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.UNASSIGN_FROM_CUSTOMER); - if (edge.getCustomerId() == null || edge.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - throw new IncorrectParameterException("Edge isn't assigned to any customer!"); - } - Customer customer = checkCustomerId(edge.getCustomerId(), Operation.READ); - - Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(getCurrentUser().getTenantId(), edgeId)); - - tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, - ComponentLifecycleEvent.UPDATED); - - logEntityAction(edgeId, edge, - edge.getCustomerId(), - ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEdgeId, customer.getId().toString(), customer.getName()); - - sendEntityAssignToCustomerNotificationMsg(savedEdge.getTenantId(), savedEdge.getId(), - customer.getId(), EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER); - - return savedEdge; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.EDGE), null, - null, - ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEdgeId); - throw handleException(e); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.UNASSIGN_FROM_CUSTOMER); + if (edge.getCustomerId() == null || edge.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { + throw new IncorrectParameterException("Edge isn't assigned to any customer!"); } + Customer customer = checkCustomerId(edge.getCustomerId(), Operation.READ); + + return tbEdgeService.unassignEdgeFromCustomer(edge, customer, getCurrentUser()); } @ApiOperation(value = "Make edge publicly available (assignEdgeToPublicCustomer)", @@ -338,26 +254,9 @@ public class EdgeController extends BaseController { public Edge assignEdgeToPublicCustomer(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); - try { - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER); - Customer publicCustomer = customerService.findOrCreatePublicCustomer(edge.getTenantId()); - Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, publicCustomer.getId())); - - tbClusterService.broadcastEntityStateChangeEvent(getTenantId(), edgeId, - ComponentLifecycleEvent.UPDATED); - - logEntityAction(edgeId, savedEdge, - savedEdge.getCustomerId(), - ActionType.ASSIGNED_TO_CUSTOMER, null, strEdgeId, publicCustomer.getId().toString(), publicCustomer.getName()); - - return savedEdge; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.EDGE), null, - null, - ActionType.ASSIGNED_TO_CUSTOMER, e, strEdgeId); - throw handleException(e); - } + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER); + return tbEdgeService.assignEdgeToPublicCustomer(getTenantId(), edgeId, getCurrentUser()); } @ApiOperation(value = "Get Tenant Edges (getTenantEdges)", @@ -455,29 +354,12 @@ public class EdgeController extends BaseController { @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException { checkParameter(EDGE_ID, strEdgeId); checkParameter("ruleChainId", strRuleChainId); - try { - RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); - checkRuleChain(ruleChainId, Operation.WRITE); - - EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); - Edge edge = checkEdgeId(edgeId, Operation.WRITE); - accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.WRITE, - edge.getId(), edge); - - Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId); - - tbClusterService.broadcastEntityStateChangeEvent(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED); - - logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null); - - return updatedEdge; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.EDGE), - null, - null, - ActionType.UPDATED, e, strEdgeId); - throw handleException(e); - } + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); + checkRuleChain(ruleChainId, Operation.WRITE); + EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); + Edge edge = checkEdgeId(edgeId, Operation.WRITE); + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.WRITE, edge.getId(), edge); + return tbEdgeService.setEdgeRootRuleChain(edge, ruleChainId, getCurrentUser()); } @ApiOperation(value = "Get Customer Edges (getCustomerEdges)", @@ -693,12 +575,6 @@ public class EdgeController extends BaseController { throw new DataValidationException("Root edge rule chain is not available!"); } - return edgeBulkImportService.processBulkImport(request, user, importedAssetInfo -> { - try { - onEdgeCreatedOrUpdated(user.getTenantId(), importedAssetInfo.getEntity(), edgeTemplateRootRuleChain, importedAssetInfo.isUpdated(), user); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + return edgeBulkImportService.processBulkImport(request, user); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java index 002956924d..281a13a6a0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java @@ -28,6 +28,7 @@ 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.RequestPart; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; @@ -49,6 +50,8 @@ import org.thingsboard.server.service.security.permission.Resource; import java.nio.ByteBuffer; +import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; +import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES; import static org.thingsboard.server.controller.ControllerConstants.OTA_PACKAGE_DESCRIPTION; @@ -105,7 +108,7 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package Info (getOtaPackageInfoById)", notes = "Fetch the OTA Package Info object based on the provided OTA Package Id. " + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + produces = APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET) @ResponseBody @@ -123,7 +126,7 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package (getOtaPackageById)", notes = "Fetch the OTA Package object based on the provided OTA Package Id. " + "The server checks that the OTA Package is owned by the same tenant. " + OTA_PACKAGE_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + produces = APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET) @ResponseBody @@ -144,8 +147,8 @@ public class OtaPackageController extends BaseController { "Specify existing OTA Package id to update the OTA Package Info. " + "Referencing non-existing OTA Package Id will cause 'Not Found' error. " + "\n\nOTA Package combination of the title with the version is unique in the scope of tenant. " + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json", - consumes = "application/json") + produces = APPLICATION_JSON_VALUE, + consumes = APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/otaPackage", method = RequestMethod.POST) @ResponseBody @@ -168,9 +171,10 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Save OTA Package data (saveOtaPackageData)", notes = "Update the OTA Package. Adds the date to the existing OTA Package Info" + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + produces = APPLICATION_JSON_VALUE, + consumes = MULTIPART_FORM_DATA_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST) + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST, consumes = MULTIPART_FORM_DATA_VALUE) @ResponseBody public OtaPackageInfo saveOtaPackageData(@ApiParam(value = OTA_PACKAGE_ID_PARAM_DESCRIPTION) @PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, @@ -179,7 +183,7 @@ public class OtaPackageController extends BaseController { @ApiParam(value = "OTA Package checksum algorithm.", allowableValues = OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES) @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, @ApiParam(value = "OTA Package data.") - @RequestBody MultipartFile file) throws ThingsboardException { + @RequestPart MultipartFile file) throws ThingsboardException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); try { @@ -221,7 +225,7 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", notes = "Returns a page of OTA Package Info objects owned by tenant. " + PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + produces = APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/otaPackages", method = RequestMethod.GET) @ResponseBody @@ -246,7 +250,7 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", notes = "Returns a page of OTA Package Info objects owned by tenant. " + PAGE_DATA_PARAMETERS + OTA_PACKAGE_INFO_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, - produces = "application/json") + produces = APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET) @ResponseBody @@ -278,7 +282,7 @@ public class OtaPackageController extends BaseController { @ApiOperation(value = "Delete OTA Package (deleteOtaPackage)", notes = "Deletes the OTA Package. Referencing non-existing OTA Package Id will cause an error. " + "Can't delete the OTA Package if it is referenced by existing devices or device profile." + TENANT_AUTHORITY_PARAGRAPH, - produces = "application/json") + produces = APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE) @ResponseBody diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueController.java b/application/src/main/java/org/thingsboard/server/controller/QueueController.java index 2d07ea8caa..3b7b407b47 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueController.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entity.queue.TbQueueService; +import org.thingsboard.server.service.entitiy.queue.TbQueueService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; 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 e7fd8e24dc..5e18f1e3a5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -36,11 +36,9 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; 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.plugin.ComponentLifecycleEvent; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entity.tenant.TbTenantService; -import org.thingsboard.server.service.install.InstallScripts; +import org.thingsboard.server.service.entitiy.tenant.TbTenantService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -69,7 +67,6 @@ public class TenantController extends BaseController { private static final String TENANT_INFO_DESCRIPTION = "The Tenant Info object extends regular Tenant object and includes Tenant Profile name. "; - private final InstallScripts installScripts; private final TenantService tenantService; private final TbTenantService tbTenantService; @@ -122,27 +119,10 @@ public class TenantController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenant", method = RequestMethod.POST) @ResponseBody - public Tenant saveTenant( - @ApiParam(value = "A JSON value representing the tenant.") - @RequestBody Tenant tenant) throws ThingsboardException { - try { - boolean newTenant = tenant.getId() == null; - - checkEntity(tenant.getId(), tenant, Resource.TENANT); - - tenant = checkNotNull(tbTenantService.saveTenant(tenant)); - if (newTenant) { - installScripts.createDefaultRuleChains(tenant.getId()); - installScripts.createDefaultEdgeRuleChains(tenant.getId()); - } - tenantProfileCache.evict(tenant.getId()); - tbClusterService.onTenantChange(tenant, null); - tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), - newTenant ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - return tenant; - } catch (Exception e) { - throw handleException(e); - } + public Tenant saveTenant(@ApiParam(value = "A JSON value representing the tenant.") + @RequestBody Tenant tenant) throws ThingsboardException { + checkEntity(tenant.getId(), tenant, Resource.TENANT); + return tbTenantService.save(tenant); } @ApiOperation(value = "Delete Tenant (deleteTenant)", @@ -150,20 +130,12 @@ public class TenantController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteTenant( - @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) - @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { + public void deleteTenant(@ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) + @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { checkParameter(TENANT_ID, strTenantId); - try { - TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); - Tenant tenant = checkTenantId(tenantId, Operation.DELETE); - tenantService.deleteTenant(tenantId); - tenantProfileCache.evict(tenantId); - tbClusterService.onTenantDelete(tenant, null); - tbClusterService.broadcastEntityStateChangeEvent(tenantId, tenantId, ComponentLifecycleEvent.DELETED); - } catch (Exception e) { - throw handleException(e); - } + TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); + Tenant tenant = checkTenantId(tenantId, Operation.DELETE); + tbTenantService.delete(tenant); } @ApiOperation(value = "Get Tenants (getTenants)", notes = "Returns a page of tenants registered in the platform. " + PAGE_DATA_PARAMETERS + SYSTEM_AUTHORITY_PARAGRAPH) 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 f8e821e489..40956d0fcc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -38,7 +38,7 @@ 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.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entity.tenant_profile.TbTenantProfileService; +import org.thingsboard.server.service.entitiy.tenant_profile.TbTenantProfileService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 49d320e3e9..4c8e4333a6 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -216,8 +216,10 @@ public class ThingsboardInstallService { dataUpdateService.updateData("3.3.2"); case "3.3.3": log.info("Upgrading ThingsBoard from version 3.3.3 to 3.3.4 ..."); - log.info("Updating system data..."); databaseEntitiesUpgradeService.upgradeDatabase("3.3.3"); + case "3.3.4": + log.info("Upgrading ThingsBoard from version 3.3.4 to 3.4.0 ..."); + log.info("Updating system data..."); systemDataLoaderService.updateSystemWidgets(); break; 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 6e8e8c20e4..7ccfd6fe4d 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 @@ -16,6 +16,8 @@ package org.thingsboard.server.service.apiusage; 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.apache.commons.lang3.StringUtils; import org.checkerframework.checker.nullness.qual.Nullable; @@ -23,9 +25,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; @@ -49,8 +51,8 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfigurat import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.tools.SchedulerUtils; -import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -58,11 +60,9 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -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.cluster.TbClusterService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; @@ -79,7 +79,6 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -87,7 +86,7 @@ import java.util.stream.Collectors; @Slf4j @Service -public class DefaultTbApiUsageStateService extends TbApplicationEventListener implements TbApiUsageStateService { +public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @@ -102,23 +101,22 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener myUsageStates = new ConcurrentHashMap<>(); + final Map myUsageStates = new ConcurrentHashMap<>(); // Entities that should be processed on other servers - private final Map otherUsageStates = new ConcurrentHashMap<>(); + final Map otherUsageStates = new ConcurrentHashMap<>(); - private final Set deletedEntities = Collections.newSetFromMap(new ConcurrentHashMap<>()); + final Set deletedEntities = Collections.newSetFromMap(new ConcurrentHashMap<>()); @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -133,33 +131,42 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener msg, TbCallback callback) { ToUsageStatsServiceMsg statsMsg = msg.getValue(); @@ -216,19 +223,6 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { - return !partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition(); - }); - otherUsageStates.entrySet().removeIf(entry -> { - return partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition(); - }); - initStatesFromDataBase(); - } - } - @Override public ApiUsageState getApiUsageState(TenantId tenantId) { TenantApiUsageState tenantState = (TenantApiUsageState) myUsageStates.get(tenantId); @@ -242,17 +236,12 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener entityIds = partitionedEntities.get(tpi); + if (entityIds != null) { + entityIds.add(entityId); + myUsageStates.put(entityId, state); + } else { + log.debug("[{}] belongs to external partition {}", entityId, tpi.getFullTopicName()); + throw new RuntimeException(entityId.getEntityType() + " belongs to external partition " + tpi.getFullTopicName() + "!"); + } + } + private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, TenantProfileConfiguration oldData, TenantProfileConfiguration newData) { long ts = System.currentTimeMillis(); @@ -339,6 +340,11 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener result) { log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result); apiUsageStateService.update(state.getApiUsageState()); @@ -420,7 +426,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener + partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition()); + } + + @Override + protected void onAddedPartitions(Set addedPartitions) { try { log.info("Initializing tenant states."); updateLock.lock(); try { - ExecutorService tmpInitExecutor = ThingsBoardExecutors.newWorkStealingPool(20, "init-tenant-states-from-db"); - try { - PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); - List> futures = new ArrayList<>(); - for (Tenant tenant : tenantIterator) { - if (!myUsageStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { + PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); + List> futures = new ArrayList<>(); + for (Tenant tenant : tenantIterator) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()); + if (addedPartitions.contains(tpi)) { + if (!myUsageStates.containsKey(tenant.getId()) && tpi.isMyPartition()) { log.debug("[{}] Initializing tenant state.", tenant.getId()); - futures.add(tmpInitExecutor.submit(() -> { + futures.add(dbExecutor.submit(() -> { try { updateTenantState((TenantApiUsageState) getOrFetchState(tenant.getId(), tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); log.debug("[{}] Initialized tenant state.", tenant.getId()); } catch (Exception e) { log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); } + return null; })); } + } else { + log.debug("[{}][{}] Tenant doesn't belong to current partition. tpi [{}]", tenant.getName(), tenant.getId(), tpi); } - for (Future future : futures) { - future.get(); - } - } finally { - tmpInitExecutor.shutdownNow(); } + Futures.whenAllComplete(futures); } finally { updateLock.unlock(); } - log.info("Initialized tenant states."); + log.info("Initialized {} tenant states.", myUsageStates.size()); } catch (Exception e) { log.warn("Unknown failure", e); } @@ -521,6 +538,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { private final AssetService assetService; + private final TbAssetService tbAssetService; @Override protected void setEntityFields(Asset entity, Map fields) { @@ -61,8 +64,9 @@ public class AssetBulkImportService extends AbstractBulkImportService { } @Override - protected Asset saveEntity(Asset entity, Map fields) { - return assetService.saveAsset(entity); + @SneakyThrows + protected Asset saveEntity(SecurityUser user, Asset entity, Map fields) { + return tbAssetService.save(entity, user); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java index d24bfed212..34c30d5225 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java @@ -34,11 +34,13 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -47,11 +49,13 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.device.TbDeviceService; import org.thingsboard.server.service.importing.AbstractBulkImportService; import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.security.model.SecurityUser; import java.util.Collection; +import java.util.Collections; import java.util.EnumSet; import java.util.Map; import java.util.Objects; @@ -65,6 +69,7 @@ import java.util.concurrent.locks.ReentrantLock; @RequiredArgsConstructor public class DeviceBulkImportService extends AbstractBulkImportService { protected final DeviceService deviceService; + protected final TbDeviceService tbDeviceService; protected final DeviceCredentialsService deviceCredentialsService; protected final DeviceProfileService deviceProfileService; @@ -96,7 +101,8 @@ public class DeviceBulkImportService extends AbstractBulkImportService { } @Override - protected Device saveEntity(Device entity, Map fields) { + @SneakyThrows + protected Device saveEntity(SecurityUser user, Device entity, Map fields) { DeviceCredentials deviceCredentials; try { deviceCredentials = createDeviceCredentials(fields); @@ -115,7 +121,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService { } entity.setDeviceProfileId(deviceProfile.getId()); - return deviceService.saveDeviceWithCredentials(entity, deviceCredentials); + return tbDeviceService.saveDeviceWithCredentials(user.getTenantId(), entity, deviceCredentials, user); } @Override @@ -225,11 +231,14 @@ public class DeviceBulkImportService extends AbstractBulkImportService { deviceProfile.setTransportType(DeviceTransportType.LWM2M); deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); + transportConfiguration.setBootstrap(Collections.emptyList()); + transportConfiguration.setClientLwM2mSettings(new OtherConfiguration(1,1,1, PowerMode.DRX, null, null, null, null, null)); + transportConfiguration.setObserveAttr(new TelemetryMappingConfiguration(Collections.emptyMap(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptyMap())); + DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - DeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); - deviceProfileData.setConfiguration(configuration); deviceProfileData.setTransportConfiguration(transportConfiguration); deviceProfileData.setProvisionConfiguration(provisionConfiguration); 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 8309fa7ef6..6b5e519f80 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,10 +16,16 @@ 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.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -38,11 +44,9 @@ 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.cluster.TbClusterService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.io.IOException; import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -76,29 +80,29 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { @Autowired private CustomerEdgeProcessor customerProcessor; - private ExecutorService tsCallBackExecutor; + private ExecutorService dbCallBackExecutor; @PostConstruct public void initExecutor() { - tsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-notifications")); + dbCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("edge-notifications")); } @PreDestroy public void shutdownExecutor() { - if (tsCallBackExecutor != null) { - tsCallBackExecutor.shutdownNow(); + if (dbCallBackExecutor != null) { + dbCallBackExecutor.shutdownNow(); } } @Override - public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException { + public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception { edge.setRootRuleChainId(ruleChainId); - Edge savedEdge = edgeService.saveEdge(edge, true); - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null); + Edge savedEdge = edgeService.saveEdge(edge); + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null).get(); return savedEdge; } - private void saveEdgeEvent(TenantId tenantId, + private ListenableFuture saveEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventType type, EdgeEventActionType action, @@ -107,28 +111,24 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { log.debug("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]", tenantId, edgeId, type, action, entityId, body); - EdgeEvent edgeEvent = new EdgeEvent(); - edgeEvent.setEdgeId(edgeId); - edgeEvent.setTenantId(tenantId); - edgeEvent.setType(type); - edgeEvent.setAction(action); - if (entityId != null) { - edgeEvent.setEntityId(entityId.getId()); - } - edgeEvent.setBody(body); - edgeEventService.save(edgeEvent); - clusterService.onEdgeEventUpdate(tenantId, edgeId); + EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); + + return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> { + clusterService.onEdgeEventUpdate(tenantId, edgeId); + return null; + }, dbCallBackExecutor); } @Override public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { - log.trace("Pushing notification to edge {}", edgeNotificationMsg); + log.debug("Pushing notification to edge {}", edgeNotificationMsg); try { TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); + ListenableFuture future; switch (type) { case EDGE: - edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg); + future = edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg); break; case USER: case ASSET: @@ -137,32 +137,47 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { case ENTITY_VIEW: case DASHBOARD: case RULE_CHAIN: - entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg); + future = entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case CUSTOMER: - customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg); + future = customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg); break; case WIDGETS_BUNDLE: case WIDGET_TYPE: - entityProcessor.processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + future = entityProcessor.processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); break; case ALARM: - alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg); + future = alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg); break; case RELATION: - relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg); + future = relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg); break; default: - log.debug("Edge event type [{}] is not designed to be pushed to edge", type); + log.warn("Edge event type [{}] is not designed to be pushed to edge", type); + future = Futures.immediateFuture(null); } + Futures.addCallback(future, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void unused) { + callback.onSuccess(); + } + + @Override + public void onFailure(Throwable throwable) { + callBackFailure(edgeNotificationMsg, callback, throwable); + } + }, dbCallBackExecutor); } catch (Exception e) { - callback.onFailure(e); - log.error("Can't push to edge updates, edgeNotificationMsg [{}]", edgeNotificationMsg, e); - } finally { - callback.onSuccess(); + callBackFailure(edgeNotificationMsg, callback, e); } } + private void callBackFailure(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback, Throwable throwable) { + String errMsg = String.format("Can't push to edge updates, edgeNotificationMsg [%s]", edgeNotificationMsg); + log.error(errMsg, throwable); + callback.onFailure(throwable); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java index ff393a2b3d..038dcadfc8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java @@ -18,13 +18,17 @@ package org.thingsboard.server.service.edge; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.edge.TbEdgeService; import org.thingsboard.server.service.importing.AbstractBulkImportService; import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.security.model.SecurityUser; @@ -37,6 +41,8 @@ import java.util.Optional; @RequiredArgsConstructor public class EdgeBulkImportService extends AbstractBulkImportService { private final EdgeService edgeService; + private final TbEdgeService tbEdgeService; + private final RuleChainService ruleChainService; @Override protected void setEntityFields(Edge entity, Map fields) { @@ -66,9 +72,11 @@ public class EdgeBulkImportService extends AbstractBulkImportService { entity.setAdditionalInfo(additionalInfo); } + @SneakyThrows @Override - protected Edge saveEntity(Edge entity, Map fields) { - return edgeService.saveEdge(entity, true); + protected Edge saveEntity(SecurityUser user, Edge entity, Map fields) { + RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(user.getTenantId()); + return tbEdgeService.saveEdge(entity, edgeTemplateRootRuleChain, user); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java index be6741cef6..ee36c80454 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java @@ -21,11 +21,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos; -import java.io.IOException; - public interface EdgeNotificationService { - Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException; + Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception; void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java deleted file mode 100644 index 0aeac4c16a..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeEventUtils.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright © 2016-2022 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.fasterxml.jackson.databind.JsonNode; -import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.EdgeId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; - -public final class EdgeEventUtils { - - private EdgeEventUtils() { - } - - public static EdgeEvent constructEdgeEvent(TenantId tenantId, - EdgeId edgeId, - EdgeEventType type, - EdgeEventActionType action, - EntityId entityId, - JsonNode body) { - EdgeEvent edgeEvent = new EdgeEvent(); - edgeEvent.setTenantId(tenantId); - edgeEvent.setEdgeId(edgeId); - edgeEvent.setType(type); - edgeEvent.setAction(action); - if (entityId != null) { - edgeEvent.setEntityId(entityId.getId()); - } - edgeEvent.setBody(body); - return edgeEvent; - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 65ca6b4ea6..c56fcd4ed9 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.edge.rpc; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import io.grpc.Server; @@ -71,7 +70,6 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i private final ConcurrentMap sessionNewEventsLocks = new ConcurrentHashMap<>(); private final Map sessionNewEvents = new HashMap<>(); private final ConcurrentMap> sessionEdgeEventChecks = new ConcurrentHashMap<>(); - private static final ObjectMapper mapper = new ObjectMapper(); @Value("${edges.rpc.port}") private int rpcPort; @@ -159,7 +157,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i @Override public StreamObserver handleMsgs(StreamObserver outputStream) { - return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, mapper, sendDownlinkExecutorService).getInputStream(); + return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, sendDownlinkExecutorService).getInputStream(); } @Override 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 fdcbdbd849..de0cea14d6 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 @@ -16,7 +16,6 @@ package org.thingsboard.server.service.edge.rpc; import com.datastax.oss.driver.api.core.uuid.Uuids; -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; @@ -96,7 +95,6 @@ public final class EdgeGrpcSession implements Closeable { private final UUID sessionId; private final BiConsumer sessionOpenListener; private final Consumer sessionCloseListener; - private final ObjectMapper mapper; private final EdgeSessionState sessionState = new EdgeSessionState(); @@ -112,13 +110,12 @@ public final class EdgeGrpcSession implements Closeable { private ScheduledExecutorService sendDownlinkExecutorService; EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver outputStream, BiConsumer sessionOpenListener, - Consumer sessionCloseListener, ObjectMapper mapper, ScheduledExecutorService sendDownlinkExecutorService) { + Consumer sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService) { this.sessionId = UUID.randomUUID(); this.ctx = ctx; this.outputStream = outputStream; this.sessionOpenListener = sessionOpenListener; this.sessionCloseListener = sessionCloseListener; - this.mapper = mapper; this.sendDownlinkExecutorService = sendDownlinkExecutorService; initInputStream(); } @@ -402,11 +399,11 @@ public final class EdgeGrpcSession implements Closeable { Runnable sendDownlinkMsgsTask = () -> { try { if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) { + List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values()); if (!firstRun) { - log.warn("[{}] Failed to deliver the batch: {}", this.sessionId, sessionState.getPendingMsgsMap().values()); + log.warn("[{}] Failed to deliver the batch: {}", this.sessionId, copy); } - log.trace("[{}] [{}] downlink msg(s) are going to be send.", this.sessionId, sessionState.getPendingMsgsMap().values().size()); - List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values()); + log.trace("[{}] [{}] downlink msg(s) are going to be send.", this.sessionId, copy.size()); for (DownlinkMsg downlinkMsg : copy) { sendDownlinkMsg(ResponseMsg.newBuilder() .setDownlinkMsg(downlinkMsg) @@ -643,7 +640,9 @@ public final class EdgeGrpcSession implements Closeable { } } } catch (Exception e) { - log.error("[{}] Can't process uplink msg [{}]", this.sessionId, uplinkMsg, e); + String errMsg = String.format("[%s] Can't process uplink msg [%s]", this.sessionId, uplinkMsg); + log.error(errMsg, e); + return Futures.immediateFailedFuture(e); } return Futures.allAsList(result); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index 51b84d6a94..0f4ecf7e30 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -26,6 +26,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.text.WordUtils; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -35,7 +36,6 @@ 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.dao.settings.AdminSettingsService; -import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; import java.util.ArrayList; import java.util.Arrays; @@ -80,19 +80,19 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { List result = new ArrayList<>(); AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); - result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, + result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailSettings))); AdminSettings tenantMailSettings = convertToTenantAdminSettings(systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue()); - result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, + result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailSettings))); AdminSettings systemMailTemplates = loadMailTemplates(); - result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, + result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailTemplates))); AdminSettings tenantMailTemplates = convertToTenantAdminSettings(systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue()); - result.add(EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, + result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailTemplates))); // @voba - returns PageData object to be in sync with other fetchers diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java index e66cafda56..ead87638fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AssetsEdgeEventFetcher.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -26,7 +27,6 @@ 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.dao.asset.AssetService; -import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; @AllArgsConstructor @Slf4j @@ -41,7 +41,7 @@ public class AssetsEdgeEventFetcher extends BasePageableEdgeEventFetcher @Override EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, Asset asset) { - return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET, + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET, EdgeEventActionType.ADDED, asset.getId(), null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java index 606cd6eaca..6791ba69a6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseUsersEdgeEventFetcher.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -26,7 +27,6 @@ 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.dao.user.UserService; -import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; @Slf4j @AllArgsConstructor @@ -41,7 +41,7 @@ public abstract class BaseUsersEdgeEventFetcher extends BasePageableEdgeEventFet @Override EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, User user) { - return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java index 3f8beb861d..709c438739 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BaseWidgetsBundlesEdgeEventFetcher.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -26,7 +27,6 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.widget.WidgetsBundleService; -import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; @Slf4j @AllArgsConstructor @@ -41,7 +41,7 @@ public abstract class BaseWidgetsBundlesEdgeEventFetcher extends BasePageableEdg @Override EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, WidgetsBundle widgetsBundle) { - return EdgeEventUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGETS_BUNDLE, + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGETS_BUNDLE, EdgeEventActionType.ADDED, widgetsBundle.getId(), null); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java index adaac874a0..1d5c618a0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.fetch; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -24,7 +25,6 @@ import org.thingsboard.server.common.data.edge.EdgeEventType; 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.service.edge.rpc.EdgeEventUtils; import java.util.ArrayList; import java.util.List; @@ -41,7 +41,7 @@ public class CustomerEdgeEventFetcher implements EdgeEventFetcher { @Override public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) { List result = new ArrayList<>(); - result.add(EdgeEventUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(), + result.add(EdgeUtils.constructEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, edge.getCustomerId(), null)); // @voba - returns PageData object to be in sync with other fetchers return new PageData<>(result, 1, result.size(), false); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java index f46ab01ee5..a6e7b3307c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DashboardsEdgeEventFetcher.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.edge.rpc.fetch; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -26,7 +27,6 @@ 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.dao.dashboard.DashboardService; -import org.thingsboard.server.service.edge.rpc.EdgeEventUtils; @AllArgsConstructor @Slf4j @@ -41,7 +41,7 @@ public class DashboardsEdgeEventFetcher extends BasePageableEdgeEventFetcher processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); 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; + Alarm deletedAlarm = mapper.readValue(edgeNotificationMsg.getBody(), Alarm.class); + return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, mapper.valueToTree(deletedAlarm)); 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()); - } - } + return Futures.transformAsync(alarmFuture, alarm -> { + if (alarm == null) { + return Futures.immediateFuture(null); } - - @Override - public void onFailure(Throwable t) { - log.warn("[{}] can't find alarm by id [{}] {}", tenantId.getId(), alarmId.getId(), t); + EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType()); + if (type == null) { + return Futures.immediateFuture(null); } + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator(), pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId relatedEdgeId : pageData.getData()) { + futures.add(saveEdgeEvent(tenantId, + relatedEdgeId, + EdgeEventType.ALARM, + EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), + alarmId, + null)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); }, 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 ac261fef91..09bef32af9 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,8 +17,13 @@ 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.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -43,6 +48,7 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -62,9 +68,11 @@ 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.cluster.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; +import java.util.ArrayList; +import java.util.List; + @Slf4j public abstract class BaseEdgeProcessor { @@ -129,6 +137,9 @@ public abstract class BaseEdgeProcessor { @Autowired protected WidgetTypeService widgetTypeService; + @Autowired + protected DataValidator deviceValidator; + @Autowired protected EntityDataMsgConstructor entityDataMsgConstructor; @@ -174,27 +185,22 @@ public abstract class BaseEdgeProcessor { @Autowired protected DbCallbackExecutorService dbCallbackExecutorService; - protected void saveEdgeEvent(TenantId tenantId, - EdgeId edgeId, - EdgeEventType type, - EdgeEventActionType action, - EntityId entityId, - JsonNode body) { + protected ListenableFuture 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); - EdgeEvent edgeEvent = new EdgeEvent(); - edgeEvent.setTenantId(tenantId); - edgeEvent.setEdgeId(edgeId); - edgeEvent.setType(type); - edgeEvent.setAction(action); - if (entityId != null) { - edgeEvent.setEntityId(entityId.getId()); - } - edgeEvent.setBody(body); - edgeEventService.save(edgeEvent); - tbClusterService.onEdgeEventUpdate(tenantId, edgeId); + EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); + + return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> { + tbClusterService.onEdgeEventUpdate(tenantId, edgeId); + return null; + }, dbCallbackExecutorService); } protected CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity, Edge edge) { @@ -205,19 +211,21 @@ public abstract class BaseEdgeProcessor { } } - protected void processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { + protected ListenableFuture processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageData pageData; + List> futures = new ArrayList<>(); do { pageData = edgeService.findEdgesByTenantId(tenantId, pageLink); if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { for (Edge edge : pageData.getData()) { - saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, null); + futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, null)); } if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java index 6c670e47ce..3554057482 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; @@ -35,6 +37,8 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Component @@ -70,7 +74,7 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } - public void processCustomerNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + public ListenableFuture processCustomerNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()); @@ -79,22 +83,24 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor { case UPDATED: PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageData pageData; + List> futures = new ArrayList<>(); do { pageData = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink); if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { for (Edge edge : pageData.getData()) { - saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null); + futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null)); } if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } } while (pageData != null && pageData.hasNext()); - break; + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); case DELETED: EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); - saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null); - break; + return saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null); + default: + return Futures.immediateFuture(null); } } 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 20c8ac7933..0e1dee7132 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 @@ -26,7 +26,6 @@ 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.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; @@ -45,11 +44,14 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; 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.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; @@ -59,7 +61,6 @@ 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.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import java.util.UUID; @@ -79,56 +80,72 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { String deviceName = deviceUpdateMsg.getName(); Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); if (device != null) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink); - boolean update = false; - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - if (pageData.getData().contains(edge.getId())) { - update = true; - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - - if (update) { - log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " + - "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg); - updateDevice(tenantId, edge, deviceUpdateMsg); - } else { - log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." + - "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg); - String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15); - Device newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); - ObjectNode body = mapper.createObjectNode(); - body.put("conflictName", deviceName); - 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); + boolean deviceAlreadyExistsForThisEdge = isDeviceAlreadyExistsOnCloudForThisEdge(tenantId, edge, device); + if (deviceAlreadyExistsForThisEdge) { + log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " + + "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg); + return updateDevice(tenantId, edge, deviceUpdateMsg); + } else { + log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." + + "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg); + String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15); + Device newDevice; + try { + newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); + } catch (DataValidationException e) { + String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg); + log.error(errMsg, e); + return Futures.immediateFuture(null); } - } while (pageData != null && pageData.hasNext()); + ObjectNode body = mapper.createObjectNode(); + body.put("conflictName", deviceName); + ListenableFuture input = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body); + return Futures.transformAsync(input, unused -> + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null), + dbCallbackExecutorService); + } } else { log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); - device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null); + try { + device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); + } catch (DataValidationException e) { + String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg); + log.error(errMsg, e); + return Futures.immediateFuture(null); + } + return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null); } - break; case ENTITY_UPDATED_RPC_MESSAGE: - updateDevice(tenantId, edge, deviceUpdateMsg); - break; + return updateDevice(tenantId, edge, deviceUpdateMsg); case ENTITY_DELETED_RPC_MESSAGE: DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); Device deviceToDelete = deviceService.findDeviceById(tenantId, deviceId); if (deviceToDelete != null) { deviceService.unassignDeviceFromEdge(tenantId, deviceId, edge.getId()); } - break; + return Futures.immediateFuture(null); case UNRECOGNIZED: + default: log.error("Unsupported msg type {}", deviceUpdateMsg.getMsgType()); return Futures.immediateFailedFuture(new RuntimeException("Unsupported msg type " + deviceUpdateMsg.getMsgType())); } - return Futures.immediateFuture(null); + } + + private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + if (pageData.getData().contains(edge.getId())) { + return true; + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return false; } public ListenableFuture processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { @@ -157,7 +174,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { } - private void updateDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { + private ListenableFuture updateDevice(TenantId tenantId, Edge edge, DeviceUpdateMsg deviceUpdateMsg) { DeviceId deviceId = new DeviceId(new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB())); Device device = deviceService.findDeviceById(tenantId, deviceId); if (device != null) { @@ -177,9 +194,11 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { } Device savedDevice = deviceService.saveDevice(device); tbClusterService.onDeviceUpdated(savedDevice, device); - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); + return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); } else { - log.warn("[{}] can't find device [{}], edge [{}]", tenantId, deviceUpdateMsg, edge.getId()); + String errMsg = String.format("[%s] can't find device [%s], edge [%s]", tenantId, deviceUpdateMsg, edge.getId()); + log.warn(errMsg); + return Futures.immediateFailedFuture(new RuntimeException(errMsg)); } } @@ -194,7 +213,6 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { if (device == null) { device = new Device(); device.setTenantId(tenantId); - device.setId(deviceId); device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); created = true; } @@ -214,6 +232,12 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { deviceUpdateMsg.getDeviceProfileIdLSB())); device.setDeviceProfileId(deviceProfileId); } + if (created) { + deviceValidator.validate(device, Device::getTenantId); + device.setId(deviceId); + } else { + deviceValidator.validate(device, Device::getTenantId); + } Device savedDevice = deviceService.saveDevice(device, false); tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false); if (created) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java index 3d662da232..fdbebafc98 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java @@ -15,11 +15,9 @@ */ package org.thingsboard.server.service.edge.rpc.processor; -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.stereotype.Component; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; @@ -33,6 +31,8 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.util.ArrayList; +import java.util.List; import java.util.UUID; @Component @@ -40,7 +40,7 @@ import java.util.UUID; @TbCoreComponent public class EdgeProcessor extends BaseEdgeProcessor { - public void processEdgeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + public ListenableFuture processEdgeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { try { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); @@ -49,54 +49,43 @@ public class EdgeProcessor extends BaseEdgeProcessor { case ASSIGNED_TO_CUSTOMER: CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId); - Futures.addCallback(edgeFuture, new FutureCallback() { - @Override - public void onSuccess(@Nullable Edge edge) { - if (edge != null && !customerId.isNullUid()) { - saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null); - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size()); - for (User user : pageData.getData()) { - saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - } - } - - @Override - public void onFailure(Throwable t) { - log.error("Can't find edge by id [{}]", edgeNotificationMsg, t); + return Futures.transformAsync(edgeFuture, edge -> { + if (edge == null || customerId.isNullUid()) { + return Futures.immediateFuture(null); } + List> futures = new ArrayList<>(); + futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null)); + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + do { + pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size()); + for (User user : pageData.getData()) { + futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); }, dbCallbackExecutorService); - break; case UNASSIGNED_FROM_CUSTOMER: CustomerId customerIdToDelete = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId); - Futures.addCallback(edgeFuture, new FutureCallback() { - @Override - public void onSuccess(@Nullable Edge edge) { - if (edge != null && !customerIdToDelete.isNullUid()) { - saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null); - } - } - - @Override - public void onFailure(Throwable t) { - log.error("Can't find edge by id [{}]", edgeNotificationMsg, t); + return Futures.transformAsync(edgeFuture, edge -> { + if (edge == null || customerIdToDelete.isNullUid()) { + return Futures.immediateFuture(null); } + return saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null); }, dbCallbackExecutorService); - break; + default: + return Futures.immediateFuture(null); } } catch (Exception e) { log.error("Exception during processing edge event", e); + return Futures.immediateFailedFuture(e); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java index d0b86ddcba..195f932b93 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java @@ -15,11 +15,9 @@ */ package org.thingsboard.server.service.edge.rpc.processor; -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.stereotype.Component; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EdgeUtils; @@ -45,6 +43,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -89,79 +88,107 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } - public void processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; + EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg); switch (actionType) { case ADDED: // used only for USER entity case UPDATED: case CREDENTIALS_UPDATED: - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId relatedEdgeId : pageData.getData()) { - saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - break; + return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); case ASSIGNED_TO_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER: - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId relatedEdgeId : pageData.getData()) { - try { - CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); - ListenableFuture future = edgeService.findEdgeByIdAsync(tenantId, relatedEdgeId); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable Edge edge) { - if (edge != null && edge.getCustomerId() != null && - !edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(customerId)) { - saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null); - } - } - - @Override - public void onFailure(Throwable t) { - log.error("Failed to find edge by id [{}] {}", edgeNotificationMsg, t); - } - }, dbCallbackExecutorService); - } catch (Exception e) { - log.error("Can't parse customer id from entity body [{}]", edgeNotificationMsg, e); - } - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - break; + return pushNotificationToAllRelatedCustomerEdges(tenantId, edgeNotificationMsg, entityId, actionType, type); case DELETED: - saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); - break; + if (edgeId != null) { + return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); + } else { + return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); + } case ASSIGNED_TO_EDGE: case UNASSIGNED_FROM_EDGE: - saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); - if (type.equals(EdgeEventType.RULE_CHAIN)) { - updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId); + ListenableFuture future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); + return Futures.transformAsync(future, unused -> { + if (type.equals(EdgeEventType.RULE_CHAIN)) { + return updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId); + } else { + return Futures.immediateFuture(null); + } + }, dbCallbackExecutorService); + default: + return Futures.immediateFuture(null); + } + } + + private ListenableFuture pushNotificationToAllRelatedCustomerEdges(TenantId tenantId, + TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, + EntityId entityId, + EdgeEventActionType actionType, + EdgeEventType type) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId relatedEdgeId : pageData.getData()) { + try { + CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); + ListenableFuture future = edgeService.findEdgeByIdAsync(tenantId, relatedEdgeId); + futures.add(Futures.transformAsync(future, edge -> { + if (edge != null && edge.getCustomerId() != null && + !edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(customerId)) { + return saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null); + } else { + return Futures.immediateFuture(null); + } + }, dbCallbackExecutorService)); + } catch (Exception e) { + log.error("Can't parse customer id from entity body [{}]", edgeNotificationMsg, e); + return Futures.immediateFailedFuture(e); + } } - break; + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + } + + private EdgeId safeGetEdgeId(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) { + return new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); + } else { + return null; } } - private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { + private ListenableFuture pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId relatedEdgeId : pageData.getData()) { + futures.add(saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + } + + private ListenableFuture updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageData pageData; + List> futures = new ArrayList<>(); do { pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { @@ -172,12 +199,12 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor { if (connectionInfos != null && !connectionInfos.isEmpty()) { for (RuleChainConnectionInfo connectionInfo : connectionInfos) { if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) { - saveEdgeEvent(tenantId, + futures.add(saveEdgeEvent(tenantId, edgeId, EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.UPDATED, ruleChain.getId(), - null); + null)); } } } @@ -188,9 +215,10 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor { } } } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } - public void processEntityNotificationForAllEdges(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + public ListenableFuture processEntityNotificationForAllEdges(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); @@ -198,8 +226,9 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor { case ADDED: case UPDATED: case DELETED: - processActionForAllEdges(tenantId, type, actionType, entityId); - break; + return processActionForAllEdges(tenantId, type, actionType, entityId); + default: + return Futures.immediateFuture(null); } } } 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 d49ac7094c..f86ea8aad2 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 @@ -126,24 +126,29 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor { .build(); } - public void processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { + public ListenableFuture processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { EntityRelation relation = mapper.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); - if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && - !relation.getTo().getEntityType().equals(EntityType.EDGE)) { - Set uniqueEdgeIds = new HashSet<>(); - uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getTo())); - uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getFrom())); - if (!uniqueEdgeIds.isEmpty()) { - for (EdgeId edgeId : uniqueEdgeIds) { - saveEdgeEvent(tenantId, - edgeId, - EdgeEventType.RELATION, - EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), - null, - mapper.valueToTree(relation)); - } - } + if (relation.getFrom().getEntityType().equals(EntityType.EDGE) || + relation.getTo().getEntityType().equals(EntityType.EDGE)) { + return Futures.immediateFuture(null); + } + + Set uniqueEdgeIds = new HashSet<>(); + uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getTo())); + uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getFrom())); + if (uniqueEdgeIds.isEmpty()) { + return Futures.immediateFuture(null); + } + List> futures = new ArrayList<>(); + for (EdgeId edgeId : uniqueEdgeIds) { + futures.add(saveEdgeEvent(tenantId, + edgeId, + EdgeEventType.RELATION, + EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), + null, + mapper.valueToTree(relation))); } + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } private List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { 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 d69133434a..9d87cabb2c 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 @@ -26,6 +26,7 @@ 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.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; @@ -71,9 +72,7 @@ import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; 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.cluster.TbClusterService; import java.util.ArrayList; import java.util.HashMap; @@ -122,13 +121,13 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { @Override public ListenableFuture processRuleChainMetadataRequestMsg(TenantId tenantId, Edge edge, RuleChainMetadataRequestMsg ruleChainMetadataRequestMsg) { log.trace("[{}] processRuleChainMetadataRequestMsg [{}][{}]", tenantId, edge.getName(), ruleChainMetadataRequestMsg); - if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() != 0 && ruleChainMetadataRequestMsg.getRuleChainIdLSB() != 0) { - RuleChainId ruleChainId = - new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB())); - saveEdgeEvent(tenantId, edge.getId(), - EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null); + if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() == 0 || ruleChainMetadataRequestMsg.getRuleChainIdLSB() == 0) { + return Futures.immediateFuture(null); } - return Futures.immediateFuture(null); + RuleChainId ruleChainId = + new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB())); + return saveEdgeEvent(tenantId, edge.getId(), + EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null); } @Override @@ -138,63 +137,72 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { EntityType.valueOf(attributesRequestMsg.getEntityType()), new UUID(attributesRequestMsg.getEntityIdMSB(), attributesRequestMsg.getEntityIdLSB())); final EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType()); - if (type != null) { - SettableFuture futureToSet = SettableFuture.create(); - String scope = attributesRequestMsg.getScope(); - ListenableFuture> findAttrFuture = attributesService.findAll(tenantId, entityId, scope); - Futures.addCallback(findAttrFuture, new FutureCallback>() { - @Override - public void onSuccess(@Nullable List ssAttributes) { - if (ssAttributes != null && !ssAttributes.isEmpty()) { - try { - Map entityData = new HashMap<>(); - ObjectNode attributes = mapper.createObjectNode(); - for (AttributeKvEntry attr : ssAttributes) { - if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { - attributes.put(attr.getKey(), attr.getBooleanValue().get()); - } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) { - attributes.put(attr.getKey(), attr.getDoubleValue().get()); - } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) { - attributes.put(attr.getKey(), attr.getLongValue().get()); - } else { - attributes.put(attr.getKey(), attr.getValueAsString()); - } - } - entityData.put("kv", attributes); - entityData.put("scope", scope); - JsonNode body = mapper.valueToTree(entityData); - log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body); - saveEdgeEvent(tenantId, - edge.getId(), - type, - EdgeEventActionType.ATTRIBUTES_UPDATED, - entityId, - body); - } catch (Exception 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, - edge.getName(), - entityId.getEntityType(), - entityId.getId()); - } + if (type == null) { + log.warn("[{}] Type doesn't supported {}", tenantId, entityId.getEntityType()); + return Futures.immediateFuture(null); + } + SettableFuture futureToSet = SettableFuture.create(); + String scope = attributesRequestMsg.getScope(); + ListenableFuture> findAttrFuture = attributesService.findAll(tenantId, entityId, scope); + Futures.addCallback(findAttrFuture, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List ssAttributes) { + if (ssAttributes == null || ssAttributes.isEmpty()) { + log.trace("[{}][{}] No attributes found for entity {} [{}]", tenantId, + edge.getName(), + entityId.getEntityType(), + entityId.getId()); futureToSet.set(null); + return; } - @Override - public void onFailure(Throwable t) { - log.error("Can't find attributes [{}]", attributesRequestMsg, t); - futureToSet.setException(t); + try { + Map entityData = new HashMap<>(); + ObjectNode attributes = mapper.createObjectNode(); + for (AttributeKvEntry attr : ssAttributes) { + if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { + attributes.put(attr.getKey(), attr.getBooleanValue().get()); + } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) { + attributes.put(attr.getKey(), attr.getDoubleValue().get()); + } else if (attr.getDataType() == DataType.LONG && attr.getLongValue().isPresent()) { + attributes.put(attr.getKey(), attr.getLongValue().get()); + } else { + attributes.put(attr.getKey(), attr.getValueAsString()); + } + } + entityData.put("kv", attributes); + entityData.put("scope", scope); + JsonNode body = mapper.valueToTree(entityData); + log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body); + ListenableFuture future = saveEdgeEvent(tenantId, edge.getId(), type, EdgeEventActionType.ATTRIBUTES_UPDATED, entityId, body); + Futures.addCallback(future, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void unused) { + futureToSet.set(null); + } + + @Override + public void onFailure(Throwable throwable) { + String errMsg = String.format("[%s] Failed to save edge event [%s]", edge.getId(), attributesRequestMsg); + log.error(errMsg, throwable); + futureToSet.setException(new RuntimeException(errMsg, throwable)); + } + }, dbCallbackExecutorService); + } catch (Exception e) { + String errMsg = String.format("[%s] Failed to save attribute updates to the edge [%s]", edge.getId(), attributesRequestMsg); + log.error(errMsg, e); + futureToSet.setException(new RuntimeException(errMsg, e)); } - }, dbCallbackExecutorService); - return futureToSet; - } else { - log.warn("[{}] Type doesn't supported {}", tenantId, entityId.getEntityType()); - return Futures.immediateFuture(null); - } + } + + @Override + public void onFailure(Throwable t) { + String errMsg = String.format("[%s] Can't find attributes [%s]", edge.getId(), attributesRequestMsg); + log.error(errMsg, t); + futureToSet.setException(new RuntimeException(errMsg, t)); + } + }, dbCallbackExecutorService); + return futureToSet; } @Override @@ -209,33 +217,49 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { futures.add(findRelationByQuery(tenantId, edge, entityId, EntitySearchDirection.TO)); ListenableFuture>> relationsListFuture = Futures.allAsList(futures); SettableFuture futureToSet = SettableFuture.create(); - Futures.addCallback(relationsListFuture, new FutureCallback>>() { + Futures.addCallback(relationsListFuture, new FutureCallback<>() { @Override public void onSuccess(@Nullable List> relationsList) { try { if (relationsList != null && !relationsList.isEmpty()) { + List> futures = new ArrayList<>(); for (List entityRelations : relationsList) { log.trace("[{}] [{}] [{}] relation(s) are going to be pushed to edge.", edge.getId(), entityId, entityRelations.size()); for (EntityRelation relation : entityRelations) { try { if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && !relation.getTo().getEntityType().equals(EntityType.EDGE)) { - saveEdgeEvent(tenantId, + futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RELATION, EdgeEventActionType.ADDED, null, - mapper.valueToTree(relation)); + mapper.valueToTree(relation))); } } catch (Exception e) { - log.error("Exception during loading relation [{}] to edge on sync!", relation, e); - futureToSet.setException(e); + String errMsg = String.format("[%s] Exception during loading relation [%s] to edge on sync!", edge.getId(), relation); + log.error(errMsg, e); + futureToSet.setException(new RuntimeException(errMsg, e)); return; } } } + Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List voids) { + futureToSet.set(null); + } + + @Override + public void onFailure(Throwable throwable) { + String errMsg = String.format("[%s] Exception during saving edge events [%s]!", edge.getId(), relationRequestMsg); + log.error(errMsg, throwable); + futureToSet.setException(new RuntimeException(errMsg, throwable)); + } + }, 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); @@ -244,8 +268,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { @Override public void onFailure(Throwable t) { - log.error("[{}] Can't find relation by query. Entity id [{}]", tenantId, entityId, t); - futureToSet.setException(t); + String errMsg = String.format("[%s] Can't find relation by query. Entity id [%s]!", tenantId, entityId); + log.error(errMsg, t); + futureToSet.setException(new RuntimeException(errMsg, t)); } }, dbCallbackExecutorService); return futureToSet; @@ -261,40 +286,42 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { @Override public ListenableFuture processDeviceCredentialsRequestMsg(TenantId tenantId, Edge edge, DeviceCredentialsRequestMsg deviceCredentialsRequestMsg) { log.trace("[{}] processDeviceCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), deviceCredentialsRequestMsg); - if (deviceCredentialsRequestMsg.getDeviceIdMSB() != 0 && deviceCredentialsRequestMsg.getDeviceIdLSB() != 0) { - DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsRequestMsg.getDeviceIdMSB(), deviceCredentialsRequestMsg.getDeviceIdLSB())); - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, - EdgeEventActionType.CREDENTIALS_UPDATED, deviceId, null); + if (deviceCredentialsRequestMsg.getDeviceIdMSB() == 0 || deviceCredentialsRequestMsg.getDeviceIdLSB() == 0) { + return Futures.immediateFuture(null); } - return Futures.immediateFuture(null); + DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsRequestMsg.getDeviceIdMSB(), deviceCredentialsRequestMsg.getDeviceIdLSB())); + return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, + EdgeEventActionType.CREDENTIALS_UPDATED, deviceId, null); } @Override public ListenableFuture processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg) { log.trace("[{}] processUserCredentialsRequestMsg [{}][{}]", tenantId, edge.getName(), userCredentialsRequestMsg); - if (userCredentialsRequestMsg.getUserIdMSB() != 0 && userCredentialsRequestMsg.getUserIdLSB() != 0) { - UserId userId = new UserId(new UUID(userCredentialsRequestMsg.getUserIdMSB(), userCredentialsRequestMsg.getUserIdLSB())); - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, - EdgeEventActionType.CREDENTIALS_UPDATED, userId, null); + if (userCredentialsRequestMsg.getUserIdMSB() == 0 || userCredentialsRequestMsg.getUserIdLSB() == 0) { + return Futures.immediateFuture(null); } - return Futures.immediateFuture(null); + UserId userId = new UserId(new UUID(userCredentialsRequestMsg.getUserIdMSB(), userCredentialsRequestMsg.getUserIdLSB())); + return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.USER, + EdgeEventActionType.CREDENTIALS_UPDATED, userId, null); } @Override public ListenableFuture processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg) { log.trace("[{}] processDeviceProfileDevicesRequestMsg [{}][{}]", tenantId, edge.getName(), deviceProfileDevicesRequestMsg); - if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() != 0 && deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() != 0) { - DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB())); - DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); - if (deviceProfileById != null) { - syncDevices(tenantId, edge, deviceProfileById.getName()); - } + if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() == 0 || deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() == 0) { + return Futures.immediateFuture(null); + } + DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB())); + DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); + if (deviceProfileById == null) { + return Futures.immediateFuture(null); } - return Futures.immediateFuture(null); + return syncDevices(tenantId, edge, deviceProfileById.getName()); } - private void syncDevices(TenantId tenantId, Edge edge, String deviceType) { + private ListenableFuture syncDevices(TenantId tenantId, Edge edge, String deviceType) { log.trace("[{}] syncDevices [{}][{}]", tenantId, edge.getName(), deviceType); + List> futures = new ArrayList<>(); try { PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageData pageData; @@ -303,7 +330,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()) { - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null); + futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null)); } if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -313,25 +340,26 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { } catch (Exception e) { log.error("Exception during loading edge device(s) on sync!", e); } + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } @Override public ListenableFuture processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) { log.trace("[{}] processWidgetBundleTypesRequestMsg [{}][{}]", tenantId, edge.getName(), widgetBundleTypesRequestMsg); + List> futures = new ArrayList<>(); if (widgetBundleTypesRequestMsg.getWidgetBundleIdMSB() != 0 && widgetBundleTypesRequestMsg.getWidgetBundleIdLSB() != 0) { WidgetsBundleId widgetsBundleId = new WidgetsBundleId(new UUID(widgetBundleTypesRequestMsg.getWidgetBundleIdMSB(), widgetBundleTypesRequestMsg.getWidgetBundleIdLSB())); WidgetsBundle widgetsBundleById = widgetsBundleService.findWidgetsBundleById(tenantId, widgetsBundleId); if (widgetsBundleById != null) { List widgetTypesToPush = widgetTypeService.findWidgetTypesByTenantIdAndBundleAlias(widgetsBundleById.getTenantId(), widgetsBundleById.getAlias()); - for (WidgetType widgetType : widgetTypesToPush) { - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null); + futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.WIDGET_TYPE, EdgeEventActionType.ADDED, widgetType.getId(), null)); } } } - return Futures.immediateFuture(null); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } @Override @@ -344,46 +372,35 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { Futures.addCallback(entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId), new FutureCallback<>() { @Override public void onSuccess(@Nullable List entityViews) { - try { - if (entityViews != null && !entityViews.isEmpty()) { - List> futures = new ArrayList<>(); - for (EntityView entityView : entityViews) { - 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)) { - saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ENTITY_VIEW, - EdgeEventActionType.ADDED, entityView.getId(), null); - } - } - @Override - public void onFailure(Throwable t) { - // Do nothing - error handles in allAsList - } - }, dbCallbackExecutorService); + if (entityViews == null || entityViews.isEmpty()) { + futureToSet.set(null); + return; + } + List> futures = new ArrayList<>(); + for (EntityView entityView : entityViews) { + ListenableFuture future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE); + futures.add(Futures.transformAsync(future, result -> { + if (Boolean.TRUE.equals(result)) { + return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ENTITY_VIEW, + EdgeEventActionType.ADDED, entityView.getId(), null); + } else { + return Futures.immediateFuture(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("Exception during loading relation [{}] to edge on sync!", t, t); - futureToSet.setException(t); - } - }, dbCallbackExecutorService); - } else { + }, dbCallbackExecutorService)); + } + Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List result) { futureToSet.set(null); } - } catch (Exception e) { - log.error("Exception during loading relation(s) to edge on sync!", e); - futureToSet.setException(e); - } + + @Override + public void onFailure(Throwable t) { + log.error("Exception during loading relation to edge on sync!", t); + futureToSet.setException(t); + } + }, dbCallbackExecutorService); } @Override @@ -395,7 +412,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { return futureToSet; } - private void saveEdgeEvent(TenantId tenantId, + private ListenableFuture saveEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventType type, EdgeEventActionType action, @@ -404,10 +421,12 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { 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); + EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body); - edgeEventService.save(edgeEvent); - tbClusterService.onEdgeEventUpdate(tenantId, edgeId); + return Futures.transform(edgeEventService.saveAsync(edgeEvent), unused -> { + tbClusterService.onEdgeEventUpdate(tenantId, edgeId); + return null; + }, dbCallbackExecutorService); } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java new file mode 100644 index 0000000000..e2ccc1f3d2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java @@ -0,0 +1,201 @@ +/** + * Copyright © 2016-2022 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.entitiy; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmQuery; +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.AlarmId; +import org.thingsboard.server.common.data.id.CustomerId; +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.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.device.ClaimDevicesService; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.service.action.EntityActionService; +import org.thingsboard.server.service.edge.EdgeNotificationService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; + +import javax.mail.MessagingException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +public abstract class AbstractTbEntityService { + + protected static final int DEFAULT_PAGE_SIZE = 1000; + + private static final ObjectMapper json = new ObjectMapper(); + + @Value("${server.log_controller_error_stack_trace}") + @Getter + private boolean logControllerErrorStackTrace; + @Value("${edges.enabled}") + @Getter + protected boolean edgesEnabled; + + @Autowired + protected DbCallbackExecutorService dbExecutor; + @Autowired + protected TbNotificationEntityService notificationEntityService; + @Autowired(required = false) + protected EdgeService edgeService; + @Autowired + protected AlarmService alarmService; + @Autowired + protected EntityActionService entityActionService; + @Autowired + protected DeviceService deviceService; + @Autowired + protected AssetService assetService; + @Autowired + protected DeviceCredentialsService deviceCredentialsService; + @Autowired + protected TenantService tenantService; + @Autowired + protected CustomerService customerService; + @Autowired + protected ClaimDevicesService claimDevicesService; + @Autowired + protected TbTenantProfileCache tenantProfileCache; + @Autowired + protected RuleChainService ruleChainService; + @Autowired + protected EdgeNotificationService edgeNotificationService; + + protected ListenableFuture removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) { + ListenableFuture> alarmsFuture = + alarmService.findAlarms(tenantId, new AlarmQuery(entityId, new TimePageLink(Integer.MAX_VALUE), null, null, false)); + + ListenableFuture> alarmIdsFuture = Futures.transform(alarmsFuture, page -> + page.getData().stream().map(AlarmInfo::getId).collect(Collectors.toList()), dbExecutor); + + return Futures.transform(alarmIdsFuture, ids -> { + ids.stream().map(alarmId -> alarmService.deleteAlarm(tenantId, alarmId)).collect(Collectors.toList()); + return null; + }, dbExecutor); + } + + protected void logEntityAction(User user, TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { + if (user != null) { + entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); + } else if (e == null) { + entityActionService.pushEntityActionToRuleEngine(entityId, entity, tenantId, customerId, actionType, null, additionalInfo); + } + } + + protected T checkNotNull(T reference) throws ThingsboardException { + return checkNotNull(reference, "Requested item wasn't found!"); + } + + protected T checkNotNull(T reference, String notFoundMessage) throws ThingsboardException { + if (reference == null) { + throw new ThingsboardException(notFoundMessage, ThingsboardErrorCode.ITEM_NOT_FOUND); + } + return reference; + } + + protected T checkNotNull(Optional reference) throws ThingsboardException { + return checkNotNull(reference, "Requested item wasn't found!"); + } + + protected T checkNotNull(Optional reference, String notFoundMessage) throws ThingsboardException { + if (reference.isPresent()) { + return reference.get(); + } else { + throw new ThingsboardException(notFoundMessage, ThingsboardErrorCode.ITEM_NOT_FOUND); + } + } + + protected ThingsboardException handleException(Exception exception) { + return handleException(exception, true); + } + + protected ThingsboardException handleException(Exception exception, boolean logException) { + if (logException && logControllerErrorStackTrace) { + log.error("Error [{}]", exception.getMessage(), exception); + } + + String cause = ""; + if (exception.getCause() != null) { + cause = exception.getCause().getClass().getCanonicalName(); + } + + if (exception instanceof ThingsboardException) { + return (ThingsboardException) exception; + } else if (exception instanceof IllegalArgumentException || exception instanceof IncorrectParameterException + || exception instanceof DataValidationException || cause.contains("IncorrectParameterException")) { + return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } else if (exception instanceof MessagingException) { + return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); + } else { + return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.GENERAL); + } + } + + @SuppressWarnings("unchecked") + protected I emptyId(EntityType entityType) { + return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); + } + + protected List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { + if (!edgesEnabled) { + return null; + } + if (EntityType.EDGE.equals(entityId.getEntityType())) { + return Collections.singletonList(new EdgeId(entityId.getId())); + } + PageDataIterableByTenantIdEntityId relatedEdgeIdsIterator = + new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE); + List result = new ArrayList<>(); + for (EdgeId edgeId : relatedEdgeIdsIterator) { + result.add(edgeId); + } + return result; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java new file mode 100644 index 0000000000..a41c5b2f87 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -0,0 +1,262 @@ +/** + * Copyright © 2016-2022 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.entitiy; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; +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.HasName; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +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.data.security.DeviceCredentials; +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.queue.util.TbCoreComponent; +import org.thingsboard.server.service.action.EntityActionService; +import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.List; + +@Slf4j +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class DefaultTbNotificationEntityService implements TbNotificationEntityService { + private static final ObjectMapper json = new ObjectMapper(); + + private final EntityActionService entityActionService; + private final TbClusterService tbClusterService; + private final GatewayNotificationsService gatewayNotificationsService; + + @Override + public void notifyEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, SecurityUser user, Exception e, + Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, customerId, actionType, user, e, additionalInfo); + } + + @Override + public void notifyDeleteEntity(TenantId tenantId, I entityId, E entity, + CustomerId customerId, List relatedEdgeIds, + SecurityUser user, Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, customerId, ActionType.DELETED, user, additionalInfo); + sendDeleteNotificationMsg(tenantId, entityId, relatedEdgeIds); + } + + @Override + public void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, + CustomerId customerId, E entity, + ActionType actionType, + EdgeEventActionType edgeActionType, + SecurityUser user, boolean sendToEdge, + Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); + + if (sendToEdge) { + sendEntityAssignToCustomerNotificationMsg(tenantId, entityId, customerId, edgeActionType); + } + } + + @Override + public void notifyAssignOrUnassignEntityToEdge(TenantId tenantId, I entityId, + CustomerId customerId, EdgeId edgeId, + E entity, ActionType actionType, + EdgeEventActionType edgeActionType, + SecurityUser user, Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); + sendEntityAssignToEdgeNotificationMsg(tenantId, edgeId, entityId, edgeActionType); + } + + @Override + public void notifyCreateOruUpdateTenant(Tenant tenant, ComponentLifecycleEvent event) { + tbClusterService.onTenantChange(tenant, null); + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), event); + } + + @Override + public void notifyDeleteTenant(Tenant tenant) { + tbClusterService.onTenantDelete(tenant, null); + tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), ComponentLifecycleEvent.DELETED); + } + + @Override + public void notifyCreateOrUpdateDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, + Device device, Device oldDevice, ActionType actionType, + SecurityUser user, Object... additionalInfo) { + tbClusterService.onDeviceUpdated(device, oldDevice); + logEntityAction(tenantId, deviceId, device, customerId, actionType, user, additionalInfo); + } + + @Override + public void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, + List relatedEdgeIds, SecurityUser user, Object... additionalInfo) { + gatewayNotificationsService.onDeviceDeleted(device); + tbClusterService.onDeviceDeleted(device, null); + + notifyDeleteEntity(tenantId, deviceId, device, customerId, relatedEdgeIds, user, additionalInfo); + } + + @Override + public void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, + DeviceCredentials deviceCredentials, SecurityUser user) { + tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceCredentials.getDeviceId(), deviceCredentials), null); + sendEntityNotificationMsg(tenantId, deviceId, EdgeEventActionType.CREDENTIALS_UPDATED); + logEntityAction(tenantId, deviceId, device, customerId, ActionType.CREDENTIALS_UPDATED, user, deviceCredentials); + } + + @Override + public void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId, + Device device, Tenant tenant, SecurityUser user, Object... additionalInfo) { + logEntityAction(tenantId, deviceId, device, customerId, ActionType.ASSIGNED_TO_TENANT, user, additionalInfo); + pushAssignedFromNotification(tenant, newTenantId, device); + } + + @Override + public void notifyCreateOrUpdateAsset(TenantId tenantId, AssetId assetId, CustomerId customerId, Asset asset, + ActionType actionType, SecurityUser user, Object... additionalInfo) { + logEntityAction(tenantId, assetId, asset, customerId, actionType, user, additionalInfo); + + if (actionType == ActionType.UPDATED) { + sendEntityNotificationMsg(asset.getTenantId(), asset.getId(), EdgeEventActionType.UPDATED); + } + } + + @Override + public void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, + SecurityUser user, Object... additionalInfo) { + ComponentLifecycleEvent lifecycleEvent; + EdgeEventActionType edgeEventActionType = null; + switch (actionType) { + case ADDED: + lifecycleEvent = ComponentLifecycleEvent.CREATED; + break; + case UPDATED: + lifecycleEvent = ComponentLifecycleEvent.UPDATED; + break; + case ASSIGNED_TO_CUSTOMER: + lifecycleEvent = ComponentLifecycleEvent.UPDATED; + edgeEventActionType = EdgeEventActionType.ASSIGNED_TO_CUSTOMER; + break; + case UNASSIGNED_FROM_CUSTOMER: + lifecycleEvent = ComponentLifecycleEvent.UPDATED; + edgeEventActionType = EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER; + break; + case DELETED: + lifecycleEvent = ComponentLifecycleEvent.DELETED; + break; + default: + throw new IllegalArgumentException("Unknown actionType: " + actionType); + } + + tbClusterService.broadcastEntityStateChangeEvent(tenantId, edgeId, lifecycleEvent); + logEntityAction(tenantId, edgeId, edge, customerId, actionType, user, additionalInfo); + + //Send notification to edge + if (edgeEventActionType != null) { + sendEntityAssignToCustomerNotificationMsg(tenantId, edgeId, customerId, edgeEventActionType); + } + } + + private void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, SecurityUser user, Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, customerId, actionType, user, null, additionalInfo); + } + + private void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, SecurityUser user, Exception e, Object... additionalInfo) { + if (user != null) { + entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); + } else if (e == null) { + entityActionService.pushEntityActionToRuleEngine(entityId, entity, tenantId, customerId, actionType, null, additionalInfo); + } + } + + private void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { + sendNotificationMsgToEdgeService(tenantId, null, entityId, null, null, action); + } + + private void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) { + try { + sendNotificationMsgToEdgeService(tenantId, null, entityId, json.writeValueAsString(customerId), null, action); + } catch (Exception e) { + log.warn("Failed to push assign/unassign to/from customer to core: {}", customerId, e); + } + } + + private void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds) { + sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null); + } + + private void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, String body) { + if (edgeIds != null && !edgeIds.isEmpty()) { + for (EdgeId edgeId : edgeIds) { + sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, null, EdgeEventActionType.DELETED); + } + } + } + + private void sendEntityAssignToEdgeNotificationMsg(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) { + sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, null, action); + } + + private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { + tbClusterService.sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, type, action); + } + + private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { + String data = entityToStr(assignedDevice); + if (data != null) { + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); + tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); + } + } + + private TbMsgMetaData getMetaDataForAssignedFrom(Tenant tenant) { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("assignedFromTenantId", tenant.getId().getId().toString()); + metaData.putValue("assignedFromTenantName", tenant.getName()); + return metaData; + } + + private String entityToStr(E entity) { + try { + return json.writeValueAsString(json.valueToTree(entity)); + } catch (JsonProcessingException e) { + log.warn("[{}] Failed to convert entity to string!", entity, e); + } + return null; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java new file mode 100644 index 0000000000..e86f068203 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2022 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.entitiy; + +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +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.data.security.DeviceCredentials; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.List; + +public interface TbNotificationEntityService { + + void notifyEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, SecurityUser user, Exception e, + Object... additionalInfo); + + void notifyDeleteEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, + List relatedEdgeIds, SecurityUser user, + Object... additionalInfo); + + void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, + CustomerId customerId, E entity, + ActionType actionType, + EdgeEventActionType edgeActionType, + SecurityUser user, boolean sendToEdge, + Object... additionalInfo); + + void notifyAssignOrUnassignEntityToEdge(TenantId tenantId, I entityId, + CustomerId customerId, EdgeId edgeId, + E entity, ActionType actionType, + EdgeEventActionType edgeActionType, + SecurityUser user, Object... additionalInfo); + + void notifyCreateOruUpdateTenant(Tenant tenant, ComponentLifecycleEvent event); + + void notifyDeleteTenant(Tenant tenant); + + void notifyCreateOrUpdateDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, + Device oldDevice, ActionType actionType, SecurityUser user, Object... additionalInfo); + + void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, + List relatedEdgeIds, SecurityUser user, Object... additionalInfo); + + void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, + DeviceCredentials deviceCredentials, SecurityUser user); + + void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId, + Device device, Tenant tenant, SecurityUser user, Object... additionalInfo); + + void notifyCreateOrUpdateAsset(TenantId tenantId, AssetId assetId, CustomerId customerId, Asset asset, + ActionType actionType, SecurityUser user, Object... additionalInfo); + + void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, SecurityUser user, Object... additionalInfo); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java new file mode 100644 index 0000000000..84b9c707d0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -0,0 +1,163 @@ +/** + * Copyright © 2016-2022 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.entitiy.asset; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.List; + +@Service +@TbCoreComponent +@AllArgsConstructor +public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService { + @Override + public Asset save(Asset asset, SecurityUser user) throws ThingsboardException { + ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = asset.getTenantId(); + try { + Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); + notificationEntityService.notifyCreateOrUpdateAsset(tenantId, savedAsset.getId(), savedAsset.getCustomerId(), asset, actionType, user); + return savedAsset; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), asset, null, actionType, user, e); + throw handleException(e); + } + } + + @Override + public ListenableFuture delete(Asset asset, SecurityUser user) throws ThingsboardException { + TenantId tenantId = asset.getTenantId(); + AssetId assetId = asset.getId(); + try { + List relatedEdgeIds = findRelatedEdgeIds(tenantId, assetId); + assetService.deleteAsset(tenantId, assetId); + notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), relatedEdgeIds, user, asset.toString()); + + return removeAlarmsByEntityId(tenantId, assetId); + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, + ActionType.DELETED, user, e, asset.toString()); + throw handleException(e); + } + } + + @Override + public Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + CustomerId customerId = customer.getId(); + try { + Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId)); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, + actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName()); + + return savedAsset; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, + actionType, user, e, assetId.toString(), customerId.toString()); + + throw handleException(e); + } + } + + @Override + public Asset unassignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; + try { + Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(tenantId, assetId)); + CustomerId customerId = customer.getId(); + + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, + actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, + true, customerId.toString(), customer.getName()); + + return savedAsset; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, + actionType, user, e, assetId.toString()); + throw handleException(e); + } + } + + @Override + public Asset assignAssetToPublicCustomer(TenantId tenantId, AssetId assetId, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + try { + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); + Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, publicCustomer.getId())); + + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, savedAsset.getCustomerId(), savedAsset, + actionType, null, user, false, actionType.toString(), + publicCustomer.getId().toString(), publicCustomer.getName()); + + return savedAsset; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, + actionType, user, e, assetId.toString()); + throw handleException(e); + } + } + + @Override + public Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, Edge edge, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_EDGE; + EdgeId edgeId = edge.getId(); + try { + Asset savedAsset = checkNotNull(assetService.assignAssetToEdge(tenantId, assetId, edgeId)); + notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, assetId, savedAsset.getCustomerId(), + edgeId, savedAsset, actionType, EdgeEventActionType.ASSIGNED_TO_EDGE, user, assetId.toString(), edgeId.toString(), edge.getName()); + + return savedAsset; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, + actionType, user, e, assetId.toString(), edgeId.toString()); + throw handleException(e); + } + } + + @Override + public Asset unassignAssetFromEdge(TenantId tenantId, Asset asset, Edge edge, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; + AssetId assetId = asset.getId(); + EdgeId edgeId = edge.getId(); + try { + Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(tenantId, assetId, edgeId)); + + notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, assetId, asset.getCustomerId(), + edgeId, asset, actionType, EdgeEventActionType.UNASSIGNED_FROM_EDGE, user, assetId.toString(), edgeId.toString(), edge.getName()); + return savedAsset; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, + actionType, user, e, assetId.toString(), edgeId.toString()); + throw handleException(e); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java new file mode 100644 index 0000000000..c6e1c29940 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2022 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.entitiy.asset; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.security.model.SecurityUser; + +public interface TbAssetService { + Asset save(Asset asset, SecurityUser user) throws ThingsboardException; + + ListenableFuture delete(Asset asset, SecurityUser user) throws ThingsboardException; + + Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException; + + Asset unassignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException; + + Asset assignAssetToPublicCustomer(TenantId tenantId, AssetId assetId, SecurityUser user) throws ThingsboardException; + + Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, Edge edge, SecurityUser user) throws ThingsboardException; + + Asset unassignAssetFromEdge(TenantId tenantId, Asset asset, Edge edge, SecurityUser user) throws ThingsboardException; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java new file mode 100644 index 0000000000..1bed17f941 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -0,0 +1,279 @@ +/** + * Copyright © 2016-2022 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.entitiy.device; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; +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.data.security.DeviceCredentials; +import org.thingsboard.server.dao.device.claim.ClaimResponse; +import org.thingsboard.server.dao.device.claim.ClaimResult; +import org.thingsboard.server.dao.device.claim.ReclaimResult; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; +import org.thingsboard.server.service.security.model.SecurityUser; + +import java.util.List; + +@AllArgsConstructor +@TbCoreComponent +@Service +@Slf4j +public class DefaultTbDeviceService extends AbstractTbEntityService implements TbDeviceService { + + @Override + public Device save(TenantId tenantId, Device device, Device oldDevice, String accessToken, SecurityUser user) throws ThingsboardException { + ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + try { + Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); + notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(), + savedDevice, oldDevice, actionType, user); + + return savedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), device, null, actionType, user, e); + throw handleException(e); + } + } + + @Override + public Device saveDeviceWithCredentials(TenantId tenantId, Device device, DeviceCredentials credentials, SecurityUser user) throws ThingsboardException { + ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + try { + Device savedDevice = checkNotNull(deviceService.saveDeviceWithCredentials(device, credentials)); + notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(), + savedDevice, device, actionType, user); + + return savedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), device, null, actionType, user, e); + throw handleException(e); + } + } + + @Override + public ListenableFuture deleteDevice(Device device, SecurityUser user) throws ThingsboardException { + TenantId tenantId = device.getTenantId(); + DeviceId deviceId = device.getId(); + try { + List relatedEdgeIds = findRelatedEdgeIds(tenantId, deviceId); + deviceService.deleteDevice(tenantId, deviceId); + notificationEntityService.notifyDeleteDevice(tenantId, deviceId, device.getCustomerId(), device, + relatedEdgeIds, user, deviceId.toString()); + + return removeAlarmsByEntityId(tenantId, deviceId); + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + ActionType.DELETED, user, e, deviceId.toString()); + throw handleException(e); + } + } + + @Override + public Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + CustomerId customerId = customer.getId(); + try { + Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(user.getTenantId(), deviceId, customerId)); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, + actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName()); + + return savedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + actionType, user, e, deviceId.toString(), customerId.toString()); + throw handleException(e); + } + } + + @Override + public Device unassignDeviceFromCustomer(Device device, Customer customer, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; + TenantId tenantId = device.getTenantId(); + DeviceId deviceId = device.getId(); + try { + Device savedDevice = checkNotNull(deviceService.unassignDeviceFromCustomer(tenantId, deviceId)); + CustomerId customerId = customer.getId(); + + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, + actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, + true, customerId.toString(), customer.getName()); + + return savedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + actionType, user, e, deviceId.toString()); + throw handleException(e); + } + } + + @Override + public Device assignDeviceToPublicCustomer(TenantId tenantId, DeviceId deviceId, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + try { + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); + Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, publicCustomer.getId())); + + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, savedDevice.getCustomerId(), savedDevice, + actionType, null, user, false, deviceId.toString(), + publicCustomer.getId().toString(), publicCustomer.getName()); + + return savedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + actionType, user, e, deviceId.toString()); + throw handleException(e); + } + } + + @Override + public DeviceCredentials getDeviceCredentialsByDeviceId(Device device, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.CREDENTIALS_READ; + TenantId tenantId = device.getTenantId(); + DeviceId deviceId = device.getId(); + try { + DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId)); + notificationEntityService.notifyEntity(tenantId, deviceId, device, device.getCustomerId(), + actionType, user, null, deviceId.toString()); + return deviceCredentials; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + actionType, user, e, deviceId.toString()); + throw handleException(e); + } + } + + @Override + public DeviceCredentials updateDeviceCredentials(Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException { + TenantId tenantId = device.getTenantId(); + DeviceId deviceId = device.getId(); + try { + DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(tenantId, deviceCredentials)); + notificationEntityService.notifyUpdateDeviceCredentials(tenantId, deviceId, device.getCustomerId(), device, result, user); + return result; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + ActionType.CREDENTIALS_UPDATED, user, e, deviceCredentials); + throw handleException(e); + } + } + + @Override + public ListenableFuture claimDevice(TenantId tenantId, Device device, CustomerId customerId, String secretKey, SecurityUser user) throws ThingsboardException { + try { + ListenableFuture future = claimDevicesService.claimDevice(device, customerId, secretKey); + + return Futures.transform(future, result -> { + if (result != null && result.getResponse().equals(ClaimResponse.SUCCESS)) { + notificationEntityService.notifyEntity(tenantId, device.getId(), result.getDevice(), customerId, + ActionType.ASSIGNED_TO_CUSTOMER, user, null, device.getId().toString(), customerId.toString(), + customerService.findCustomerById(tenantId, customerId).getName()); + } + return result; + }, MoreExecutors.directExecutor()); + } catch (Exception e) { + throw handleException(e); + } + } + + @Override + public ListenableFuture reclaimDevice(TenantId tenantId, Device device, SecurityUser user) throws ThingsboardException { + try { + ListenableFuture future = claimDevicesService.reClaimDevice(tenantId, device); + + return Futures.transform(future, result -> { + Customer unassignedCustomer = result.getUnassignedCustomer(); + if (unassignedCustomer != null) { + notificationEntityService.notifyEntity(tenantId, device.getId(), device, device.getCustomerId(), ActionType.UNASSIGNED_FROM_CUSTOMER, user, null, + device.getId().toString(), unassignedCustomer.getId().toString(), unassignedCustomer.getName()); + } + return result; + }, MoreExecutors.directExecutor()); + } catch (Exception e) { + throw handleException(e); + } + } + + @Override + public Device assignDeviceToTenant(Device device, Tenant newTenant, SecurityUser user) throws ThingsboardException { + TenantId tenantId = device.getTenantId(); + TenantId newTenantId = newTenant.getId(); + try { + Tenant tenant = tenantService.findTenantById(tenantId); + Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device); + + notificationEntityService.notifyAssignDeviceToTenant(tenantId, newTenantId, device.getId(), + assignedDevice.getCustomerId(), assignedDevice, tenant, user, newTenantId.toString(), newTenant.getName()); + + return assignedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + ActionType.ASSIGNED_TO_TENANT, user, e, newTenantId.toString()); + throw handleException(e); + } + } + + @Override + public Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, Edge edge, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_EDGE; + EdgeId edgeId = edge.getId(); + try { + Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(tenantId, deviceId, edgeId)); + notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, deviceId, savedDevice.getCustomerId(), + edgeId, savedDevice, actionType, EdgeEventActionType.ASSIGNED_TO_EDGE, user, deviceId.toString(), edgeId.toString(), edge.getName()); + return savedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + actionType, user, e, deviceId.toString(), edgeId.toString()); + throw handleException(e); + } + } + + @Override + public Device unassignDeviceFromEdge(Device device, Edge edge, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; + TenantId tenantId = device.getTenantId(); + DeviceId deviceId = device.getId(); + EdgeId edgeId = edge.getId(); + try { + Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(tenantId, deviceId, edgeId)); + + notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, deviceId, device.getCustomerId(), + edgeId, device, actionType, EdgeEventActionType.UNASSIGNED_FROM_EDGE, user, deviceId.toString(), edgeId.toString(), edge.getName()); + return savedDevice; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + actionType, user, e, deviceId.toString(), edgeId.toString()); + throw handleException(e); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java new file mode 100644 index 0000000000..16fd0c7448 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2022 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.entitiy.device; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.device.claim.ClaimResult; +import org.thingsboard.server.dao.device.claim.ReclaimResult; +import org.thingsboard.server.service.security.model.SecurityUser; + +public interface TbDeviceService { + + Device save(TenantId tenantId, Device device, Device oldDevice, String accessToken, SecurityUser user) throws ThingsboardException; + + Device saveDeviceWithCredentials(TenantId tenantId, Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException; + + ListenableFuture deleteDevice(Device device, SecurityUser user) throws ThingsboardException; + + Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, SecurityUser user) throws ThingsboardException; + + Device unassignDeviceFromCustomer(Device device, Customer customer, SecurityUser user) throws ThingsboardException; + + Device assignDeviceToPublicCustomer(TenantId tenantId, DeviceId deviceId, SecurityUser user) throws ThingsboardException; + + DeviceCredentials getDeviceCredentialsByDeviceId(Device device, SecurityUser user) throws ThingsboardException; + + DeviceCredentials updateDeviceCredentials(Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException; + + ListenableFuture claimDevice(TenantId tenantId, Device device, CustomerId customerId, String secretKey, SecurityUser user) throws ThingsboardException; + + ListenableFuture reclaimDevice(TenantId tenantId, Device device, SecurityUser user) throws ThingsboardException; + + Device assignDeviceToTenant(Device device, Tenant newTenant, SecurityUser user) throws ThingsboardException; + + Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, Edge edge, SecurityUser user) throws ThingsboardException; + + Device unassignDeviceFromEdge(Device device, Edge edge, SecurityUser user) throws ThingsboardException; +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java new file mode 100644 index 0000000000..93487ac5cd --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java @@ -0,0 +1,150 @@ +/** + * Copyright © 2016-2022 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.entitiy.edge; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; +import org.thingsboard.server.service.security.model.SecurityUser; + +@AllArgsConstructor +@TbCoreComponent +@Service +@Slf4j +public class DefaultTbEdgeService extends AbstractTbEntityService implements TbEdgeService { + + @Override + public Edge saveEdge(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException { + ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = edge.getTenantId(); + try { + Edge savedEdge = checkNotNull(edgeService.saveEdge(edge)); + EdgeId edgeId = savedEdge.getId(); + + if (actionType == ActionType.ADDED) { + ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edgeId); + edgeNotificationService.setEdgeRootRuleChain(tenantId, savedEdge, edgeTemplateRootRuleChain.getId()); + edgeService.assignDefaultRuleChainsToEdge(tenantId, edgeId); + } + + notificationEntityService.notifyEdge(tenantId, edgeId, savedEdge.getCustomerId(), savedEdge, actionType, user); + + return savedEdge; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), edge, null, actionType, user, e); + throw handleException(e); + } + } + + @Override + public void deleteEdge(Edge edge, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.DELETED; + EdgeId edgeId = edge.getId(); + TenantId tenantId = edge.getTenantId(); + try { + edgeService.deleteEdge(tenantId, edgeId); + notificationEntityService.notifyEdge(tenantId, edgeId, edge.getCustomerId(), edge, actionType, user, edgeId.toString()); + + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), edge, null, actionType, user, e); + throw handleException(e); + } + } + + @Override + public Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + CustomerId customerId = customer.getId(); + try { + Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); + + notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, actionType, user, + edgeId.toString(), customerId.toString(), customer.getName()); + + return savedEdge; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, + actionType, user, e, edgeId.toString(), customerId.toString()); + throw handleException(e); + } + } + + @Override + public Edge unassignEdgeFromCustomer(Edge edge, Customer customer, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; + TenantId tenantId = edge.getTenantId(); + EdgeId edgeId = edge.getId(); + CustomerId customerId = customer.getId(); + try { + Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(tenantId, edgeId)); + + notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, actionType, user, + edgeId.toString(), customerId.toString(), customer.getName()); + return savedEdge; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, + actionType, user, e, edgeId.toString()); + throw handleException(e); + } + } + + @Override + public Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + try { + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); + CustomerId customerId = publicCustomer.getId(); + Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); + + notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, actionType, user, + edgeId.toString(), customerId.toString(), publicCustomer.getName()); + + return savedEdge; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, + actionType, user, e, edgeId.toString()); + throw handleException(e); + } + } + + @Override + public Edge setEdgeRootRuleChain(Edge edge, RuleChainId ruleChainId, SecurityUser user) throws ThingsboardException { + ActionType actionType = ActionType.UPDATED; + TenantId tenantId = edge.getTenantId(); + EdgeId edgeId = edge.getId(); + try { + Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, ruleChainId); + notificationEntityService.notifyEdge(tenantId, edgeId, null, updatedEdge, actionType, user); + return updatedEdge; + } catch (Exception e) { + notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, + actionType, user, e, edgeId.toString()); + throw handleException(e); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java new file mode 100644 index 0000000000..5ed27fbbb3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2022 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.entitiy.edge; + +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.service.security.model.SecurityUser; + +public interface TbEdgeService { + Edge saveEdge(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException; + + void deleteEdge(Edge edge, SecurityUser user) throws ThingsboardException; + + Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, SecurityUser user) throws ThingsboardException; + + Edge unassignEdgeFromCustomer(Edge edge, Customer customer, SecurityUser user) throws ThingsboardException; + + Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, SecurityUser user) throws ThingsboardException; + + Edge setEdgeRootRuleChain(Edge edge, RuleChainId ruleChainId, SecurityUser user) throws ThingsboardException; +} diff --git a/application/src/main/java/org/thingsboard/server/service/entity/queue/DefaultTbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java similarity index 99% rename from application/src/main/java/org/thingsboard/server/service/entity/queue/DefaultTbQueueService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java index 655d4beb88..60788ef02b 100644 --- a/application/src/main/java/org/thingsboard/server/service/entity/queue/DefaultTbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entity.queue; +package org.thingsboard.server.service.entitiy.queue; import lombok.AllArgsConstructor; import lombok.SneakyThrows; diff --git a/application/src/main/java/org/thingsboard/server/service/entity/queue/TbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/TbQueueService.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/entity/queue/TbQueueService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/queue/TbQueueService.java index c431d242cf..86f82b1236 100644 --- a/application/src/main/java/org/thingsboard/server/service/entity/queue/TbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/TbQueueService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entity.queue; +package org.thingsboard.server.service.entitiy.queue; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.QueueId; diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java new file mode 100644 index 0000000000..2a8fc1bf0a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2022 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.entitiy.tenant; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; +import org.thingsboard.server.service.entitiy.queue.TbQueueService; +import org.thingsboard.server.service.install.InstallScripts; + +import java.util.Collections; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class DefaultTbTenantService extends AbstractTbEntityService implements TbTenantService { + + private final InstallScripts installScripts; + private final TbQueueService tbQueueService; + private final TenantProfileService tenantProfileService; + + @Override + public Tenant save(Tenant tenant) throws ThingsboardException { + try { + boolean created = tenant.getId() == null; + Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; + + Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant)); + if (created) { + installScripts.createDefaultRuleChains(savedTenant.getId()); + installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); + } + tenantProfileCache.evict(savedTenant.getId()); + notificationEntityService.notifyCreateOruUpdateTenant(savedTenant, created ? + ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + + TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; + TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); + tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile); + return savedTenant; + } catch (Exception e) { + throw handleException(e); + } + } + + @Override + public void delete(Tenant tenant) throws ThingsboardException { + try { + TenantId tenantId = tenant.getId(); + tenantService.deleteTenant(tenantId); + tenantProfileCache.evict(tenantId); + notificationEntityService.notifyDeleteTenant(tenant); + } catch (Exception e) { + throw handleException(e); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/entity/tenant/TbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java similarity index 73% rename from application/src/main/java/org/thingsboard/server/service/entity/tenant/TbTenantService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java index 56b3e5e7ba..0ff7bc749c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entity/tenant/TbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entity.tenant; +package org.thingsboard.server.service.entitiy.tenant; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.exception.ThingsboardException; public interface TbTenantService { + Tenant save(Tenant tenant) throws ThingsboardException; - Tenant saveTenant(Tenant tenant); + void delete(Tenant tenant) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entity/tenant_profile/DefaultTbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/DefaultTbTenantProfileService.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/entity/tenant_profile/DefaultTbTenantProfileService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/DefaultTbTenantProfileService.java index 2a01ddfed6..23a838e72a 100644 --- a/application/src/main/java/org/thingsboard/server/service/entity/tenant_profile/DefaultTbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/DefaultTbTenantProfileService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entity.tenant_profile; +package org.thingsboard.server.service.entitiy.tenant_profile; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entity.queue.TbQueueService; +import org.thingsboard.server.service.entitiy.queue.TbQueueService; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/entity/tenant_profile/TbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/TbTenantProfileService.java similarity index 93% rename from application/src/main/java/org/thingsboard/server/service/entity/tenant_profile/TbTenantProfileService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/TbTenantProfileService.java index 0764319d03..66c3cb138c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entity/tenant_profile/TbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/TbTenantProfileService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entity.tenant_profile; +package org.thingsboard.server.service.entitiy.tenant_profile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; diff --git a/application/src/main/java/org/thingsboard/server/service/entity/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entity/tenant/DefaultTbTenantService.java deleted file mode 100644 index 807cf6b1b1..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/entity/tenant/DefaultTbTenantService.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright © 2016-2022 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.entity.tenant; - -import lombok.AllArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantProfileService; -import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entity.queue.TbQueueService; - -import java.util.Collections; - -@Slf4j -@Service -@TbCoreComponent -@AllArgsConstructor -public class DefaultTbTenantService implements TbTenantService { - - private final TenantService tenantService; - private final TbQueueService tbQueueService; - private final TenantProfileService tenantProfileService; - private final TbTenantProfileCache tenantProfileCache; - - @Override - public Tenant saveTenant(Tenant tenant) { - boolean updated = tenant.getId() != null; - Tenant oldTenant = updated ? tenantService.findTenantById(tenant.getId()) : null; - - Tenant savedTenant = tenantService.saveTenant(tenant); - tenantProfileCache.evict(savedTenant.getId()); - - TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; - TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); - //TODO: send notifications before queue update - tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile); - return savedTenant; - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java index b7b740f190..81a0068a4d 100644 --- a/application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java @@ -95,7 +95,7 @@ public abstract class AbstractBulkImportService processBulkImport(BulkImportRequest request, SecurityUser user, Consumer> onEntityImported) throws Exception { + public final BulkImportResult processBulkImport(BulkImportRequest request, SecurityUser user) throws Exception { List entitiesData = parseData(request); BulkImportResult result = new BulkImportResult<>(); @@ -106,10 +106,9 @@ public abstract class AbstractBulkImportService DonAsynchron.submit(() -> { SecurityContextHolder.setContext(securityContext); - ImportedEntityInfo importedEntityInfo = saveEntity(entityData.getFields(), user); + ImportedEntityInfo importedEntityInfo = saveEntity(entityData.getFields(), user); E entity = importedEntityInfo.getEntity(); - onEntityImported.accept(importedEntityInfo); saveKvs(user, entity, entityData.getKvs()); return importedEntityInfo; @@ -148,7 +147,7 @@ public abstract class AbstractBulkImportService fields); - protected abstract E saveEntity(E entity, Map fields); + protected abstract E saveEntity(SecurityUser user, E entity, Map fields); protected abstract EntityType getEntityType(); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 22e0869bd1..f7b9e5cad1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -521,6 +521,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } catch (Exception ignored) { } + log.info("Updating TTL cleanup procedure for the event table..."); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", "schema_event_ttl_procedure.sql"); + loadSql(schemaUpdateFile, conn); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", SCHEMA_UPDATE_SQL); + loadSql(schemaUpdateFile, conn); + log.info("Updating schema settings..."); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;"); log.info("Schema updated."); diff --git a/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java b/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java new file mode 100644 index 0000000000..bf0bc429c8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java @@ -0,0 +1,148 @@ +/** + * Copyright © 2016-2022 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.partition; + +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; + +import java.util.HashSet; +import java.util.Queue; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; + +@Slf4j +public abstract class AbstractPartitionBasedService extends TbApplicationEventListener { + + protected final ConcurrentMap> partitionedEntities = new ConcurrentHashMap<>(); + final Queue> subscribeQueue = new ConcurrentLinkedQueue<>(); + + protected ListeningScheduledExecutorService scheduledExecutor; + + abstract protected String getServiceName(); + + abstract protected String getSchedulerExecutorName(); + + abstract protected void onAddedPartitions(Set addedPartitions); + + abstract protected void cleanupEntityOnPartitionRemoval(T entityId); + + public Set getPartitionedEntities(TopicPartitionInfo tpi) { + return partitionedEntities.get(tpi); + } + + protected void init() { + // Should be always single threaded due to absence of locks. + scheduledExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(getSchedulerExecutorName()))); + } + + protected ServiceType getServiceType() { + return ServiceType.TB_CORE; + } + + protected void stop() { + if (scheduledExecutor != null) { + scheduledExecutor.shutdownNow(); + } + } + + /** + * DiscoveryService will call this event from the single thread (one-by-one). + * Events order is guaranteed by DiscoveryService. + * The only concurrency is expected from the [main] thread on Application started. + * Async implementation. Locks is not allowed by design. + * Any locks or delays in this module will affect DiscoveryService and entire system + */ + @Override + protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + if (getServiceType().equals(partitionChangeEvent.getServiceType())) { + log.debug("onTbApplicationEvent, processing event: {}", partitionChangeEvent); + subscribeQueue.add(partitionChangeEvent.getPartitions()); + scheduledExecutor.submit(this::pollInitStateFromDB); + } + } + + protected void pollInitStateFromDB() { + final Set partitions = getLatestPartitions(); + if (partitions == null) { + log.debug("Nothing to do. Partitions are empty."); + return; + } + initStateFromDB(partitions); + } + + private void initStateFromDB(Set partitions) { + try { + log.info("[{}] CURRENT PARTITIONS: {}", getServiceName(), partitionedEntities.keySet()); + log.info("[{}] NEW PARTITIONS: {}", getServiceName(), partitions); + + Set addedPartitions = new HashSet<>(partitions); + addedPartitions.removeAll(partitionedEntities.keySet()); + + log.info("[{}] ADDED PARTITIONS: {}", getServiceName(), addedPartitions); + + Set removedPartitions = new HashSet<>(partitionedEntities.keySet()); + removedPartitions.removeAll(partitions); + + log.info("[{}] REMOVED PARTITIONS: {}", getServiceName(), removedPartitions); + + // We no longer manage current partition of entities; + removedPartitions.forEach(partition -> { + Set entities = partitionedEntities.remove(partition); + entities.forEach(this::cleanupEntityOnPartitionRemoval); + }); + + onRepartitionEvent(); + + addedPartitions.forEach(tpi -> partitionedEntities.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet())); + + if (!addedPartitions.isEmpty()) { + onAddedPartitions(addedPartitions); + } + + log.info("[{}] Managing following partitions:", getServiceName()); + partitionedEntities.forEach((tpi, entities) -> { + log.info("[{}][{}]: {} entities", getServiceName(), tpi.getFullTopicName(), entities.size()); + }); + } catch (Throwable t) { + log.warn("[{}] Failed to init entities state from DB", getServiceName(), t); + } + } + + protected void onRepartitionEvent() { + } + + private Set getLatestPartitions() { + log.debug("getLatestPartitionsFromQueue, queue size {}", subscribeQueue.size()); + Set partitions = null; + while (!subscribeQueue.isEmpty()) { + partitions = subscribeQueue.poll(); + log.debug("polled from the queue partitions {}", partitions); + } + log.debug("getLatestPartitionsFromQueue, partitions {}", partitions); + return partitions; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java index b4337fb9f3..d2a6369c51 100644 --- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java +++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java @@ -29,12 +29,16 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.common.util.KvUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; +import org.thingsboard.server.common.data.query.ComplexFilterPredicate; +import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; @@ -42,6 +46,10 @@ import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataSortOrder; import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.FilterPredicateType; +import org.thingsboard.server.common.data.query.KeyFilter; +import org.thingsboard.server.common.data.query.KeyFilterPredicate; +import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entity.EntityService; @@ -52,6 +60,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import java.util.ArrayList; import java.util.Collection; @@ -59,7 +68,9 @@ import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -93,9 +104,81 @@ public class DefaultEntityQueryService implements EntityQueryService { @Override public PageData findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) { + if (query.getKeyFilters() != null) { + resolveDynamicValuesInPredicates( + query.getKeyFilters().stream() + .map(KeyFilter::getPredicate) + .collect(Collectors.toList()), + securityUser + ); + } return entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query); } + private void resolveDynamicValuesInPredicates(List predicates, SecurityUser user) { + predicates.forEach(predicate -> { + if (predicate.getType() == FilterPredicateType.COMPLEX) { + resolveDynamicValuesInPredicates( + ((ComplexFilterPredicate) predicate).getPredicates(), + user + ); + } else { + setResolvedValue(user, (SimpleKeyFilterPredicate) predicate); + } + }); + } + + private void setResolvedValue(SecurityUser user, SimpleKeyFilterPredicate predicate) { + DynamicValue dynamicValue = predicate.getValue().getDynamicValue(); + if (dynamicValue != null && dynamicValue.getResolvedValue() == null) { + resolveDynamicValue(dynamicValue, user, predicate.getType()); + } + } + + private void resolveDynamicValue(DynamicValue dynamicValue, SecurityUser user, FilterPredicateType predicateType) { + EntityId entityId; + switch (dynamicValue.getSourceType()) { + case CURRENT_TENANT: + entityId = user.getTenantId(); + break; + case CURRENT_CUSTOMER: + entityId = user.getCustomerId(); + break; + case CURRENT_USER: + entityId = user.getId(); + break; + default: + throw new RuntimeException("Not supported operation for source type: {" + dynamicValue.getSourceType() + "}"); + } + + try { + Optional valueOpt = attributesService.find(user.getTenantId(), entityId, + TbAttributeSubscriptionScope.SERVER_SCOPE.name(), dynamicValue.getSourceAttribute()).get(); + + if (valueOpt.isPresent()) { + AttributeKvEntry entry = valueOpt.get(); + Object resolved = null; + switch (predicateType) { + case STRING: + resolved = KvUtil.getStringValue(entry); + break; + case NUMERIC: + resolved = KvUtil.getDoubleValue(entry); + break; + case BOOLEAN: + resolved = KvUtil.getBoolValue(entry); + break; + case COMPLEX: + break; + } + + dynamicValue.setResolvedValue((T) resolved); + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + @Override public PageData findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) { EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java index 41871e3dae..23ba6fd19e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java @@ -31,7 +31,7 @@ public class CustomerUserPermissions extends AbstractPermissions { public CustomerUserPermissions() { super(); - put(Resource.ALARM, TenantAdminPermissions.tenantEntityPermissionChecker); + put(Resource.ALARM, customerAlarmPermissionChecker); put(Resource.ASSET, customerEntityPermissionChecker); put(Resource.DEVICE, customerEntityPermissionChecker); put(Resource.CUSTOMER, customerPermissionChecker); @@ -44,6 +44,19 @@ public class CustomerUserPermissions extends AbstractPermissions { put(Resource.RPC, rpcPermissionChecker); } + private static final PermissionChecker customerAlarmPermissionChecker = new PermissionChecker() { + @Override + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + if (!(entity instanceof HasCustomerId)) { + return false; + } + return user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId()); + } + }; + private static final PermissionChecker customerEntityPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS, Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES, @@ -62,10 +75,7 @@ public class CustomerUserPermissions extends AbstractPermissions { if (!(entity instanceof HasCustomerId)) { return false; } - if (!operation.equals(Operation.CLAIM_DEVICES) && !user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId())) { - return false; - } - return true; + return operation.equals(Operation.CLAIM_DEVICES) || user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId()); } }; @@ -78,10 +88,7 @@ public class CustomerUserPermissions extends AbstractPermissions { if (!super.hasPermission(user, operation, entityId, entity)) { return false; } - if (!user.getCustomerId().equals(entityId)) { - return false; - } - return true; + return user.getCustomerId().equals(entityId); } }; @@ -98,10 +105,7 @@ public class CustomerUserPermissions extends AbstractPermissions { if (!user.getTenantId().equals(dashboard.getTenantId())) { return false; } - if (!dashboard.isAssignedToCustomer(user.getCustomerId())) { - return false; - } - return true; + return dashboard.isAssignedToCustomer(user.getCustomerId()); } }; @@ -113,10 +117,7 @@ public class CustomerUserPermissions extends AbstractPermissions { if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) { return false; } - if (!user.getId().equals(userId)) { - return false; - } - return true; + return user.getId().equals(userId); } }; @@ -132,10 +133,7 @@ public class CustomerUserPermissions extends AbstractPermissions { if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) { return true; } - if (!user.getTenantId().equals(entity.getTenantId())) { - return false; - } - return true; + return user.getTenantId().equals(entity.getTenantId()); } }; @@ -151,10 +149,7 @@ public class CustomerUserPermissions extends AbstractPermissions { if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) { return true; } - if (!user.getTenantId().equals(entity.getTenantId())) { - return false; - } - return true; + return user.getTenantId().equals(entity.getTenantId()); } }; } diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java index 2591790137..b42dcb6b83 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java @@ -19,9 +19,11 @@ import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.sms.SmsSender; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; import org.thingsboard.server.common.data.sms.config.AwsSnsSmsProviderConfiguration; +import org.thingsboard.server.common.data.sms.config.SmppSmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.TwilioSmsProviderConfiguration; import org.thingsboard.server.service.sms.aws.AwsSmsSender; +import org.thingsboard.server.service.sms.smpp.SmppSmsSender; import org.thingsboard.server.service.sms.twilio.TwilioSmsSender; @Component @@ -34,6 +36,8 @@ public class DefaultSmsSenderFactory implements SmsSenderFactory { return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config); case TWILIO: return new TwilioSmsSender((TwilioSmsProviderConfiguration)config); + case SMPP: + return new SmppSmsSender((SmppSmsProviderConfiguration) config); default: throw new RuntimeException("Unknown SMS provider type " + config.getType()); } diff --git a/application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java b/application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java new file mode 100644 index 0000000000..57c5c5d25c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sms/smpp/SmppSmsSender.java @@ -0,0 +1,186 @@ +/** + * Copyright © 2016-2022 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.sms.smpp; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.smpp.Connection; +import org.smpp.Data; +import org.smpp.Session; +import org.smpp.TCPIPConnection; +import org.smpp.TimeoutException; +import org.smpp.WrongSessionStateException; +import org.smpp.pdu.Address; +import org.smpp.pdu.BindReceiver; +import org.smpp.pdu.BindRequest; +import org.smpp.pdu.BindResponse; +import org.smpp.pdu.BindTransciever; +import org.smpp.pdu.BindTransmitter; +import org.smpp.pdu.PDUException; +import org.smpp.pdu.SubmitSM; +import org.smpp.pdu.SubmitSMResp; +import org.thingsboard.rule.engine.api.sms.exception.SmsException; +import org.thingsboard.server.common.data.sms.config.SmppSmsProviderConfiguration; +import org.thingsboard.server.service.sms.AbstractSmsSender; + +import java.io.IOException; +import java.util.Optional; + +@Slf4j +public class SmppSmsSender extends AbstractSmsSender { + protected SmppSmsProviderConfiguration config; + + protected Session smppSession; + + public SmppSmsSender(SmppSmsProviderConfiguration config) { + if (config.getBindType() == null) { + config.setBindType(SmppSmsProviderConfiguration.SmppBindType.TX); + } + if (StringUtils.isNotEmpty(config.getSourceAddress())) { + if (config.getSourceTon() == null) { + config.setSourceTon((byte) 5); + } + if (config.getSourceNpi() == null) { + config.setSourceNpi((byte) 0); + } + } + if (config.getDestinationTon() == null) { + config.setDestinationTon((byte) 5); + } + if (config.getDestinationNpi() == null) { + config.setDestinationNpi((byte) 0); + } + + this.config = config; + this.smppSession = initSmppSession(); + } + + private SmppSmsSender() {} // for testing purposes + + + @Override + public int sendSms(String numberTo, String message) throws SmsException { + try { + checkSmppSession(); + + SubmitSM request = new SubmitSM(); + if (StringUtils.isNotEmpty(config.getServiceType())) { + request.setServiceType(config.getServiceType()); + } + if (StringUtils.isNotEmpty(config.getSourceAddress())) { + request.setSourceAddr(new Address(config.getSourceTon(), config.getSourceNpi(), config.getSourceAddress())); + } + request.setDestAddr(new Address(config.getDestinationTon(), config.getDestinationNpi(), prepareNumber(numberTo))); + request.setShortMessage(message); + request.setDataCoding(Optional.ofNullable(config.getCodingScheme()).orElse((byte) 0)); + request.setReplaceIfPresentFlag((byte) 0); + request.setEsmClass((byte) 0); + request.setProtocolId((byte) 0); + request.setPriorityFlag((byte) 0); + request.setRegisteredDelivery((byte) 0); + request.setSmDefaultMsgId((byte) 0); + + SubmitSMResp response = smppSession.submit(request); + + log.debug("SMPP submit command status: {}", response.getCommandStatus()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + return countMessageSegments(message); + } + + private synchronized void checkSmppSession() { + if (smppSession == null || !smppSession.isOpened()) { + smppSession = initSmppSession(); + } + } + + protected Session initSmppSession() { + try { + Connection connection = new TCPIPConnection(config.getHost(), config.getPort()); + Session session = new Session(connection); + + BindRequest bindRequest; + switch (config.getBindType()) { + case TX: + bindRequest = new BindTransmitter(); + break; + case RX: + bindRequest = new BindReceiver(); + break; + case TRX: + bindRequest = new BindTransciever(); + break; + default: + throw new UnsupportedOperationException("Unsupported bind type " + config.getBindType()); + } + + bindRequest.setSystemId(config.getSystemId()); + bindRequest.setPassword(config.getPassword()); + + byte interfaceVersion; + switch (config.getProtocolVersion()) { + case "3.3": + interfaceVersion = Data.SMPP_V33; + break; + case "3.4": + interfaceVersion = Data.SMPP_V34; + break; + default: + throw new UnsupportedOperationException("Unsupported SMPP version: " + config.getProtocolVersion()); + } + bindRequest.setInterfaceVersion(interfaceVersion); + + if (StringUtils.isNotEmpty(config.getSystemType())) { + bindRequest.setSystemType(config.getSystemType()); + } + if (StringUtils.isNotEmpty(config.getAddressRange())) { + bindRequest.setAddressRange(config.getDestinationTon(), config.getDestinationNpi(), config.getAddressRange()); + } + + BindResponse bindResponse = session.bind(bindRequest); + log.debug("SMPP bind response: {}", bindResponse.debugString()); + + if (bindResponse.getCommandStatus() != 0) { + throw new IllegalStateException("Error status when binding: " + bindResponse.getCommandStatus()); + } + + return session; + } catch (Exception e) { + throw new IllegalArgumentException("Failed to establish SMPP session: " + ExceptionUtils.getRootCauseMessage(e), e); + } + } + + private String prepareNumber(String number) { + if (config.getDestinationTon() == Data.GSM_TON_INTERNATIONAL) { + return StringUtils.removeStart(number, "+"); + } + return number; + } + + @Override + public void destroy() { + try { + smppSession.unbind(); + smppSession.close(); + } catch (TimeoutException | PDUException | IOException | WrongSessionStateException e) { + throw new RuntimeException(e); + } + + } +} 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 8a24cdbf1a..d74b0fca87 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 @@ -20,15 +20,15 @@ import com.google.common.base.Function; 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.ListeningScheduledExecutorService; -import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; +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.Tenant; @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; @@ -52,13 +53,10 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; -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.cluster.TbClusterService; +import org.thingsboard.server.service.partition.AbstractPartitionBasedService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.Nonnull; @@ -68,14 +66,11 @@ import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Queue; import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -94,7 +89,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; @Service @TbCoreComponent @Slf4j -public class DefaultDeviceStateService extends TbApplicationEventListener implements DeviceStateService { +public class DefaultDeviceStateService extends AbstractPartitionBasedService implements DeviceStateService { public static final String ACTIVITY_STATE = "active"; public static final String LAST_CONNECT_TIME = "lastConnectTime"; @@ -131,12 +126,9 @@ public class DefaultDeviceStateService extends TbApplicationEventListener> partitionedDevices = new ConcurrentHashMap<>(); - final ConcurrentMap deviceStates = new ConcurrentHashMap<>(); - final Queue> subscribeQueue = new ConcurrentLinkedQueue<>(); + final ConcurrentMap deviceStates = new ConcurrentHashMap<>(); public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService, AttributesService attributesService, TimeseriesService tsService, @@ -156,25 +148,36 @@ public class DefaultDeviceStateService extends TbApplicationEventListener 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) { updateActivityState(deviceId, stateData, lastReportedActivity); } - cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId); } void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) { @@ -208,18 +213,20 @@ public class DefaultDeviceStateService extends TbApplicationEventListener() { + Futures.addCallback(fetchDeviceState(device), new FutureCallback<>() { @Override public void onSuccess(@Nullable DeviceStateData state) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId()); - if (partitionedDevices.containsKey(tpi)) { + if (partitionedEntities.containsKey(tpi)) { addDeviceUsingState(tpi, state); save(deviceId, ACTIVITY_STATE, false); callback.onSuccess(); @@ -286,105 +296,25 @@ public class DefaultDeviceStateService extends TbApplicationEventListener partitions = getLatestPartitionsFromQueue(); - if (partitions == null) { - log.info("Device state service. Nothing to do. partitions is null"); - return; - } - initStateFromDB(partitions); - } - - // TODO: move to utils - Set getLatestPartitionsFromQueue() { - log.debug("getLatestPartitionsFromQueue, queue size {}", subscribeQueue.size()); - Set partitions = null; - while (!subscribeQueue.isEmpty()) { - partitions = subscribeQueue.poll(); - log.debug("polled from the queue partitions {}", partitions); - } - log.debug("getLatestPartitionsFromQueue, partitions {}", partitions); - return partitions; - } - - private void initStateFromDB(Set partitions) { - try { - log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet()); - log.info("NEW PARTITIONS: {}", partitions); - - Set addedPartitions = new HashSet<>(partitions); - addedPartitions.removeAll(partitionedDevices.keySet()); - - log.info("ADDED PARTITIONS: {}", addedPartitions); - - Set removedPartitions = new HashSet<>(partitionedDevices.keySet()); - removedPartitions.removeAll(partitions); - - log.info("REMOVED PARTITIONS: {}", removedPartitions); - - // We no longer manage current partition of devices; - removedPartitions.forEach(partition -> { - Set devices = partitionedDevices.remove(partition); - devices.forEach(this::cleanUpDeviceStateMap); - }); - - addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet())); - - initPartitions(addedPartitions); - - scheduledExecutor.submit(() -> { - log.info("Managing following partitions:"); - partitionedDevices.forEach((tpi, devices) -> { - log.info("[{}]: {} devices", tpi.getFullTopicName(), devices.size()); - }); - }); - } catch (Throwable t) { - log.warn("Failed to init device states from DB", t); - } - } - - //TODO 3.0: replace this dummy search with new functionality to search by partitions using SQL capabilities. - //Adding only entities that are in new partitions - boolean initPartitions(Set addedPartitions) { - if (addedPartitions.isEmpty()) { - return false; - } - - List tenants = tenantService.findTenants(new PageLink(Integer.MAX_VALUE)).getData(); - for (Tenant tenant : tenants) { + protected void onAddedPartitions(Set addedPartitions) { + PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); + for (Tenant tenant : tenantIterator) { log.debug("Finding devices for tenant [{}]", tenant.getName()); final PageLink pageLink = new PageLink(initFetchPackSize); - scheduledExecutor.submit(() -> processPageAndSubmitNextPage(addedPartitions, tenant, pageLink, scheduledExecutor)); - + processPageAndSubmitNextPage(addedPartitions, tenant, pageLink); } - return true; } - private void processPageAndSubmitNextPage(final Set addedPartitions, final Tenant tenant, final PageLink pageLink, final ExecutorService executor) { + private void processPageAndSubmitNextPage(final Set addedPartitions, final Tenant tenant, final PageLink pageLink) { log.trace("[{}] Process page {} from {}", tenant, pageLink.getPage(), pageLink.getPageSize()); List> fetchFutures = new ArrayList<>(); PageData page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink); for (Device device : page.getData()) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), device.getId()); - if (addedPartitions.contains(tpi)) { + if (addedPartitions.contains(tpi) && !deviceStates.containsKey(device.getId())) { log.debug("[{}][{}] Device belong to current partition. tpi [{}]. Fetching state from DB", device.getName(), device.getId(), tpi); - ListenableFuture future = Futures.transform(fetchDeviceState(device), new Function() { + ListenableFuture future = Futures.transform(fetchDeviceState(device), new Function<>() { @Nullable @Override public Void apply(@Nullable DeviceStateData state) { @@ -403,7 +333,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener>() { + Futures.addCallback(Futures.successfulAsList(fetchFutures), new FutureCallback<>() { @Override public void onSuccess(List result) { log.trace("[{}] Success init device state from DB for batch size {}", tenant.getId(), result.size()); @@ -419,7 +349,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener processPageAndSubmitNextPage(addedPartitions, tenant, nextPageLink, executor)); + processPageAndSubmitNextPage(addedPartitions, tenant, nextPageLink); } } @@ -435,10 +365,10 @@ public class DefaultDeviceStateService extends TbApplicationEventListener deviceIds = partitionedDevices.get(tpi); + Set deviceIds = partitionedEntities.get(tpi); if (deviceIds != null) { deviceIds.add(state.getDeviceId()); - deviceStates.put(state.getDeviceId(), state); + deviceStates.putIfAbsent(state.getDeviceId(), state); } else { log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName()); throw new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!"); @@ -447,7 +377,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener { + partitionedEntities.forEach((tpi, deviceIds) -> { log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size()); for (DeviceId deviceId : deviceIds) { updateInactivityStateIfExpired(ts, deviceId); @@ -464,16 +394,22 @@ public class DefaultDeviceStateService extends TbApplicationEventListener deviceIdSet = partitionedDevices.get(tpi); - deviceIdSet.remove(deviceId); + Set deviceIdSet = partitionedEntities.get(tpi); + if (deviceIdSet != null) { + deviceIdSet.remove(deviceId); + } } - private void cleanUpDeviceStateMap(DeviceId deviceId) { + @Override + protected void cleanupEntityOnPartitionRemoval(DeviceId deviceId) { + cleanupEntity(deviceId); + } + + private void cleanupEntity(DeviceId deviceId) { deviceStates.remove(deviceId); } + private ListenableFuture fetchDeviceState(Device device) { ListenableFuture future; if (persistToTelemetry) { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 7e16b4e243..e0eb4820fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -219,10 +219,13 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @Override public void onSuccess(@Nullable TbEntityDataSubCtx theCtx) { try { - if (cmd.getLatestCmd() != null) { - handleLatestCmd(theCtx, cmd.getLatestCmd()); - } else if (cmd.getTsCmd() != null) { - handleTimeSeriesCmd(theCtx, cmd.getTsCmd()); + if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null) { + if (cmd.getLatestCmd() != null) { + handleLatestCmd(theCtx, cmd.getLatestCmd()); + } + if (cmd.getTsCmd() != null) { + handleTimeSeriesCmd(theCtx, cmd.getTsCmd()); + } } else if (!theCtx.isInitialDataSent()) { EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription()); wsService.sendWsMsg(theCtx.getSessionId(), update); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index 874fd25c64..a8283a1e4d 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -167,7 +167,7 @@ public abstract class TbAbstractDataSubCtx subKeys) { - Map keyStates = buildKeyStats(entityData, keysType, subKeys); + Map keyStates = buildKeyStats(entityData, keysType, subKeys, true); log.trace("[{}][{}][{}] Creating attributes subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates); return TbAttributeSubscription.builder() .serviceId(serviceId) @@ -183,7 +183,7 @@ public abstract class TbAbstractDataSubCtx subKeys, boolean latestValues, long startTs, long endTs) { - Map keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys); + Map keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys, latestValues); if (!latestValues && entityData.getTimeseries() != null) { entityData.getTimeseries().forEach((k, v) -> { long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L); @@ -211,15 +211,17 @@ public abstract class TbAbstractDataSubCtx buildKeyStats(EntityData entityData, EntityKeyType keysType, List subKeys) { + private Map buildKeyStats(EntityData entityData, EntityKeyType keysType, List subKeys, boolean latestValues) { Map keyStates = new HashMap<>(); subKeys.forEach(key -> keyStates.put(key.getKey(), 0L)); - if (entityData.getLatest() != null) { + if (latestValues && entityData.getLatest() != null) { Map currentValues = entityData.getLatest().get(keysType); if (currentValues != null) { currentValues.forEach((k, v) -> { - log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs()); - keyStates.put(k, v.getTs()); + if (subKeys.contains(new EntityKey(keysType, k))) { + log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs()); + keyStates.put(k, v.getTs()); + } }); } } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java index 27090587d4..b8211e50cc 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java @@ -19,6 +19,7 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityKey; @@ -55,6 +56,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { private LatestValueCmd latestValueCmd; @Getter private final int maxEntitiesPerDataSubscription; + private Map> latestTsEntityData; public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, @@ -63,6 +65,12 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription; } + @Override + public void fetchData() { + super.fetchData(); + this.updateLatestTsData(this.data); + } + @Override protected void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues) { EntityId entityId = subToEntityIdMap.get(subscriptionUpdate.getSubscriptionId()); @@ -123,40 +131,37 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { Object[] data = (Object[]) v.get(0); tsUpdate.computeIfAbsent(k, key -> new ArrayList<>()).add(new TsValue((Long) data[0], (String) data[1])); }); - EntityData entityData = getDataForEntity(entityId); - if (entityData != null && entityData.getLatest() != null) { - Map latestCtxValues = entityData.getLatest().get(keyType); - log.trace("[{}][{}][{}] Going to compare update with {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), latestCtxValues); - if (latestCtxValues != null) { - latestCtxValues.forEach((k, v) -> { - List updateList = tsUpdate.get(k); - if (updateList != null) { - for (TsValue update : new ArrayList<>(updateList)) { - if (update.getTs() < v.getTs()) { - log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); - // Looks like this is redundant feature and our UI is ready to merge the updates. - //updateList.remove(update); - } else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) { - log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); - updateList.remove(update); - } - if (updateList.isEmpty()) { - tsUpdate.remove(k); - } + Map latestCtxValues = getLatestTsValuesForEntity(entityId); + log.trace("[{}][{}][{}] Going to compare update with {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), latestCtxValues); + if (latestCtxValues != null) { + latestCtxValues.forEach((k, v) -> { + List updateList = tsUpdate.get(k); + if (updateList != null) { + for (TsValue update : new ArrayList<>(updateList)) { + if (update.getTs() < v.getTs()) { + log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); + // Looks like this is redundant feature and our UI is ready to merge the updates. + //updateList.remove(update); + } else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) { + log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs()); + updateList.remove(update); + } + if (updateList.isEmpty()) { + tsUpdate.remove(k); } } - }); - //Setting new values - tsUpdate.forEach((k, v) -> { - Optional maxValue = v.stream().max(Comparator.comparingLong(TsValue::getTs)); - maxValue.ifPresent(max -> latestCtxValues.put(k, max)); - }); - } + } + }); + //Setting new values + tsUpdate.forEach((k, v) -> { + Optional maxValue = v.stream().max(Comparator.comparingLong(TsValue::getTs)); + maxValue.ifPresent(max -> latestCtxValues.put(k, max)); + }); } if (!tsUpdate.isEmpty()) { Map tsMap = new HashMap<>(); tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()]))); - entityData = new EntityData(entityId, null, tsMap); + EntityData entityData = new EntityData(entityId, null, tsMap); wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription)); } } @@ -165,8 +170,27 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null); } + private Map getLatestTsValuesForEntity(EntityId entityId) { + return latestTsEntityData.get(entityId); + } + + private void updateLatestTsData(PageData data) { + latestTsEntityData = new HashMap<>(); + data.getData().stream().forEach(entityData -> { + Map latestTsMap = new HashMap<>(); + latestTsEntityData.put(entityData.getEntityId(), latestTsMap); + if (entityData.getLatest() != null) { + Map latestTsValues = entityData.getLatest().get(EntityKeyType.TIME_SERIES); + if (latestTsValues != null) { + latestTsValues.forEach(latestTsMap::put); + } + } + }); + } + @Override public synchronized void doUpdate(Map newDataMap) { + this.updateLatestTsData(this.data); List subIdsToCancel = new ArrayList<>(); List subsToAdd = new ArrayList<>(); Set currentSubs = new HashSet<>(); diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java index 2c2f82bc50..1fd3892f39 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java @@ -22,7 +22,8 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.ttl.AbstractCleanUpService; + +import java.util.concurrent.TimeUnit; @TbCoreComponent @Slf4j @@ -33,10 +34,13 @@ public class EventsCleanUpService extends AbstractCleanUpService { "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.events.execution_interval_ms})}"; @Value("${sql.ttl.events.events_ttl}") - private long ttl; + private long ttlInSec; @Value("${sql.ttl.events.debug_events_ttl}") - private long debugTtl; + private long debugTtlInSec; + + @Value("${sql.ttl.events.execution_interval_ms}") + private long executionIntervalInMs; @Value("${sql.ttl.events.enabled}") private boolean ttlTaskExecutionEnabled; @@ -51,7 +55,27 @@ public class EventsCleanUpService extends AbstractCleanUpService { @Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.events.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { - eventService.cleanupEvents(ttl, debugTtl); + long ts = System.currentTimeMillis(); + long regularEventStartTs; + long regularEventEndTs; + long debugEventStartTs; + long debugEventEndTs; + + if (ttlInSec > 0) { + regularEventEndTs = ts - TimeUnit.SECONDS.toMillis(ttlInSec); + regularEventStartTs = regularEventEndTs - 2 * executionIntervalInMs; + } else { + regularEventStartTs = regularEventEndTs = 0; + } + + if (debugTtlInSec > 0) { + debugEventEndTs = ts - TimeUnit.SECONDS.toMillis(debugTtlInSec); + debugEventStartTs = debugEventEndTs - 2 * executionIntervalInMs; + } else { + debugEventStartTs = debugEventEndTs = 0; + } + + eventService.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 658a403654..9f78d79ae2 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -165,7 +165,7 @@ ui: # Help parameters help: # Base url for UI help assets - base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.3}" + base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.4}" database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records @@ -278,6 +278,16 @@ sql: stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}" + events: + batch_size: "${SQL_EVENTS_BATCH_SIZE:10000}" + batch_max_delay: "${SQL_EVENTS_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}" + batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution + edge_events: + batch_size: "${SQL_EDGE_EVENTS_BATCH_SIZE:1000}" + batch_max_delay: "${SQL_EDGE_EVENTS_BATCH_MAX_DELAY_MS:100}" + stats_print_interval_ms: "${SQL_EDGE_EVENTS_BATCH_STATS_PRINT_MS:10000}" + batch_threads: "${SQL_EDGE_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks batch_sort: "${SQL_BATCH_SORT:false}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert @@ -471,6 +481,8 @@ updates: # Enable/disable updates checking. enabled: "${UPDATES_ENABLED:true}" +spring.main.allow-circular-references: "true" + # spring freemarker configuration spring.freemarker.checkTemplateLocation: "false" @@ -480,7 +492,7 @@ spring.mvc.cors: # Intercept path "[/api/**]": #Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled. - allowed-origins: "*" + allowed-origin-patterns: "*" #Comma-separated list of methods to allow. '*' allows all methods. allowed-methods: "*" #Comma-separated list of headers to allow in a request. '*' allows all headers. diff --git a/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java b/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java index d617ed830b..0e928b7dd6 100644 --- a/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java +++ b/application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java @@ -59,11 +59,11 @@ class StatsActorTest { @Test void givenNonEmptyStatMessage_whenOnStatsPersistMsg_thenNoAction() { statsActor.onStatsPersistMsg(new StatsPersistMsg(0, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)); - verify(eventService, times(1)).save(any(Event.class)); + verify(eventService, times(1)).saveAsync(any(Event.class)); statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 0, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)); - verify(eventService, times(2)).save(any(Event.class)); + verify(eventService, times(2)).saveAsync(any(Event.class)); statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)); - verify(eventService, times(3)).save(any(Event.class)); + verify(eventService, times(3)).saveAsync(any(Event.class)); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index eae6f59198..98e4c12759 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -16,16 +16,25 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootContextLoader; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; @ActiveProfiles("test") @RunWith(SpringRunner.class) @@ -33,9 +42,49 @@ import org.springframework.test.context.web.WebAppConfiguration; @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @Configuration @ComponentScan({"org.thingsboard.server"}) -@WebAppConfiguration -@SpringBootTest() +@EnableWebSocket +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @Slf4j -public abstract class AbstractControllerTest extends AbstractWebTest { +public abstract class AbstractControllerTest extends AbstractWebTest { + + public static final String WS_URL = "ws://localhost:"; + + @LocalServerPort + protected int wsPort; + + private TbTestWebSocketClient wsClient; // lazy + + public TbTestWebSocketClient getWsClient() { + if (wsClient == null) { + synchronized (this) { + try { + if (wsClient == null) { + wsClient = buildAndConnectWebSocketClient(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + return wsClient; + } + + @Before + public void beforeWsTest() throws Exception { + // placeholder + } + + @After + public void afterWsTest() throws Exception { + if (wsClient != null) { + wsClient.close(); + } + } + + private TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { + TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token)); + assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue(); + return wsClient; + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java index 59f7714233..efa9d9f08a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractInMemoryStorageTest.java @@ -23,20 +23,4 @@ import org.thingsboard.server.queue.memory.InMemoryStorage; @Slf4j public abstract class AbstractInMemoryStorageTest { - @Before - public void setUpInMemoryStorage() { - log.info("set up InMemoryStorage"); - cleanupInMemStorage(); - } - - @After - public void tearDownInMemoryStorage() { - log.info("tear down InMemoryStorage"); - cleanupInMemStorage(); - } - - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 157e10295c..7765fd09c7 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -18,6 +18,9 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; @@ -64,8 +67,10 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.HasId; 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.page.TimePageLink; @@ -101,6 +106,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC @Slf4j public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { + public static final int TIMEOUT = 30; protected ObjectMapper mapper = new ObjectMapper(); @@ -110,7 +116,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { private static final String SYS_ADMIN_PASSWORD = "sysadmin"; protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org"; - private static final String TENANT_ADMIN_PASSWORD = "tenant"; + protected static final String TENANT_ADMIN_PASSWORD = "tenant"; protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org"; private static final String CUSTOMER_USER_PASSWORD = "customer"; @@ -129,6 +135,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected String username; protected TenantId tenantId; + protected CustomerId customerId; @SuppressWarnings("rawtypes") private HttpMessageConverter mappingJackson2HttpMessageConverter; @@ -197,6 +204,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { customer.setTitle("Customer"); customer.setTenantId(tenantId); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + customerId = savedCustomer.getId(); User customerUser = new User(); customerUser.setAuthority(Authority.CUSTOMER_USER); @@ -259,9 +267,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { } private Tenant savedDifferentTenant; + private Customer savedDifferentCustomer; protected void loginDifferentTenant() throws Exception { loginSysAdmin(); + if (savedDifferentTenant != null) { + deleteDifferentTenant(); + } + Tenant tenant = new Tenant(); tenant.setTitle("Different tenant"); savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class); @@ -274,10 +287,27 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { createUserAndLogin(differentTenantAdmin, "testPassword"); } + protected void loginDifferentCustomer() throws Exception { + loginTenantAdmin(); + Customer customer = new Customer(); + customer.setTitle("Different customer"); + savedDifferentCustomer = doPost("/api/customer", customer, Customer.class); + Assert.assertNotNull(savedDifferentCustomer); + User differentCustomerUser = new User(); + differentCustomerUser.setAuthority(Authority.CUSTOMER_USER); + differentCustomerUser.setTenantId(tenantId); + differentCustomerUser.setCustomerId(savedDifferentCustomer.getId()); + differentCustomerUser.setEmail("different_customer@thingsboard.org"); + + createUserAndLogin(differentCustomerUser, "testPassword"); + } + protected void deleteDifferentTenant() throws Exception { - loginSysAdmin(); - doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) - .andExpect(status().isOk()); + if (savedDifferentTenant != null) { + loginSysAdmin(); + doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } } protected User createUserAndLogin(User user, String password) throws Exception { @@ -357,18 +387,23 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { } } + protected DeviceProfile createDeviceProfile(String name) { + return createDeviceProfile(name, null); + } + protected DeviceProfile createDeviceProfile(String name, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(name); deviceProfile.setType(DeviceProfileType.DEFAULT); - deviceProfile.setTransportType(DeviceTransportType.DEFAULT); deviceProfile.setDescription(name + " Test"); DeviceProfileData deviceProfileData = new DeviceProfileData(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); deviceProfileData.setConfiguration(configuration); if (deviceProfileTransportConfiguration != null) { + deviceProfile.setTransportType(deviceProfileTransportConfiguration.getType()); deviceProfileData.setTransportConfiguration(deviceProfileTransportConfiguration); } else { + deviceProfile.setTransportType(DeviceTransportType.DEFAULT); deviceProfileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); } deviceProfile.setProfileData(deviceProfileData); @@ -377,10 +412,11 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return deviceProfile; } - protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration) { + protected MqttDeviceProfileTransportConfiguration createMqttDeviceProfileTransportConfiguration(TransportPayloadTypeConfiguration transportPayloadTypeConfiguration, boolean sendAckOnValidationException) { MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_TELEMETRY_TOPIC); mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); + mqttDeviceProfileTransportConfiguration.setSendAckOnValidationException(sendAckOnValidationException); mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); return mqttDeviceProfileTransportConfiguration; } @@ -483,16 +519,24 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); } - protected T doPost(String urlTemplate, Class responseClass, String... params) throws Exception { - return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); + protected T doPost(String urlTemplate, Class responseClass, String... params) { + try { + return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); + } catch (Exception e) { + throw new RuntimeException(e); + } } protected T doPost(String urlTemplate, T content, Class responseClass, ResultMatcher resultMatcher, String... params) throws Exception { return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseClass); } - protected T doPost(String urlTemplate, T content, Class responseClass, String... params) throws Exception { - return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass); + protected T doPost(String urlTemplate, T content, Class responseClass, String... params) { + try { + return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass); + } catch (Exception e) { + throw new RuntimeException(e); + } } protected R doPostWithResponse(String urlTemplate, T content, Class responseClass, String... params) throws Exception { @@ -613,4 +657,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); return edge; } + + protected > ListenableFuture> deleteEntitiesAsync(String urlTemplate, List entities, ListeningExecutorService executor) { + List> futures = new ArrayList<>(entities.size()); + for (T entity : entities) { + futures.add(executor.submit(() -> + doDelete(urlTemplate + entity.getId().getId()) + .andExpect(status().isOk()))); + } + return Futures.allAsList(futures); + } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java deleted file mode 100644 index 15884f38a0..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright © 2016-2022 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.junit.Assert; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootContextLoader; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.web.server.LocalServerPort; -import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.Configuration; -import org.springframework.test.annotation.DirtiesContext; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.ContextConfiguration; -import org.springframework.test.context.junit4.SpringRunner; -import java.net.URI; -import java.net.URISyntaxException; - -@ActiveProfiles("test") -@RunWith(SpringRunner.class) -@ContextConfiguration(classes = AbstractControllerTest.class, loader = SpringBootContextLoader.class) -@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) -@Configuration -@ComponentScan({"org.thingsboard.server"}) -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@Slf4j -public abstract class AbstractWebsocketTest extends AbstractWebTest { - - protected static final String WS_URL = "ws://localhost:"; - - @LocalServerPort - protected int wsPort; - - protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException { - TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token)); - Assert.assertTrue(wsClient.connectBlocking()); - return wsClient; - } - -} diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java new file mode 100644 index 0000000000..ce9b6916fc --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -0,0 +1,230 @@ +/** + * Copyright © 2016-2022 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 org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public abstract class BaseAlarmControllerTest extends AbstractControllerTest { + + public static final String TEST_ALARM_TYPE = "Test"; + + protected Device customerDevice; + + @Before + public void setup() throws Exception { + loginTenantAdmin(); + + Device device = new Device(); + device.setTenantId(tenantId); + device.setName("Test device"); + device.setLabel("Label"); + device.setType("Type"); + device.setCustomerId(customerId); + customerDevice = doPost("/api/device", device, Device.class); + + logout(); + } + + @After + public void teardown() throws Exception { + loginSysAdmin(); + deleteDifferentTenant(); + } + + @Test + public void testCreateAlarmViaCustomer() throws Exception { + loginCustomerUser(); + createAlarm(TEST_ALARM_TYPE); + logout(); + } + + @Test + public void testCreateAlarmViaTenant() throws Exception { + loginTenantAdmin(); + createAlarm(TEST_ALARM_TYPE); + logout(); + } + + @Test + public void testUpdateAlarmViaCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + alarm.setSeverity(AlarmSeverity.MAJOR); + Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(updatedAlarm); + Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity()); + logout(); + } + + @Test + public void testUpdateAlarmViaTenant() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + alarm.setSeverity(AlarmSeverity.MAJOR); + Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(updatedAlarm); + Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity()); + logout(); + } + + @Test + public void testUpdateAlarmViaDifferentTenant() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + alarm.setSeverity(AlarmSeverity.MAJOR); + loginDifferentTenant(); + doPost("/api/alarm", alarm).andExpect(status().isForbidden()); + logout(); + } + + @Test + public void testUpdateAlarmViaDifferentCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + loginDifferentCustomer(); + alarm.setSeverity(AlarmSeverity.MAJOR); + doPost("/api/alarm", alarm).andExpect(status().isForbidden()); + logout(); + } + + @Test + public void testDeleteAlarmViaCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk()); + logout(); + } + + @Test + public void testDeleteAlarmViaTenant() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk()); + logout(); + } + + @Test + public void testDeleteAlarmViaDifferentTenant() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + loginDifferentTenant(); + doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden()); + logout(); + } + + @Test + public void testDeleteAlarmViaAnotherCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + loginDifferentCustomer(); + doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden()); + logout(); + } + + @Test + public void testClearAlarmViaCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk()); + Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus()); + logout(); + } + + @Test + public void testClearAlarmViaTenant() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk()); + Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus()); + logout(); + } + + @Test + public void testAcknowledgeAlarmViaCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk()); + Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class); + Assert.assertNotNull(foundAlarm); + Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus()); + logout(); + } + + @Test + public void testClearAlarmViaDifferentCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + loginDifferentCustomer(); + doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden()); + logout(); + } + + @Test + public void testClearAlarmViaDifferentTenant() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + loginDifferentTenant(); + doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden()); + logout(); + } + + @Test + public void testAcknowledgeAlarmViaDifferentCustomer() throws Exception { + loginCustomerUser(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + loginDifferentCustomer(); + doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden()); + logout(); + } + + @Test + public void testAcknowledgeAlarmViaDifferentTenant() throws Exception { + loginTenantAdmin(); + Alarm alarm = createAlarm(TEST_ALARM_TYPE); + loginDifferentTenant(); + doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden()); + logout(); + } + + private Alarm createAlarm(String type) throws Exception { + Alarm alarm = Alarm.builder() + .tenantId(tenantId) + .customerId(customerId) + .originator(customerDevice.getId()) + .status(AlarmStatus.ACTIVE_UNACK) + .severity(AlarmSeverity.CRITICAL) + .type(type) + .build(); + + alarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertNotNull(alarm); + + return alarm; + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java index c44c005efb..0861ab34c8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java @@ -24,6 +24,7 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; @@ -182,6 +183,39 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { .andExpect(status().isNotFound()); } + @Test + public void testDeleteAssetAssignedToEntityView() throws Exception { + Asset asset1 = new Asset(); + asset1.setName("My asset 1"); + asset1.setType("default"); + Asset savedAsset1 = doPost("/api/asset", asset1, Asset.class); + + Asset asset2 = new Asset(); + asset2.setName("My asset 2"); + asset2.setType("default"); + Asset savedAsset2 = doPost("/api/asset", asset2, Asset.class); + + EntityView view = new EntityView(); + view.setEntityId(savedAsset1.getId()); + view.setTenantId(savedTenant.getId()); + view.setName("My entity view"); + view.setType("default"); + EntityView savedView = doPost("/api/entityView", view, EntityView.class); + + doDelete("/api/asset/" + savedAsset1.getId().getId().toString()) + .andExpect(status().isBadRequest()); + + savedView.setEntityId(savedAsset2.getId()); + + doPost("/api/entityView", savedView, EntityView.class); + + doDelete("/api/asset/" + savedAsset1.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/asset/" + savedAsset1.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + @Test public void testSaveAssetWithEmptyType() throws Exception { Asset asset = new Asset(); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java index dcf3f85a71..a7f623d2e4 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java @@ -15,16 +15,17 @@ */ package org.thingsboard.server.controller; -import static org.hamcrest.Matchers.containsString; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; +import org.junit.Assert; import org.junit.Before; +import org.junit.Test; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; @@ -32,20 +33,28 @@ 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.security.Authority; -import org.junit.Assert; -import org.junit.Test; -import com.fasterxml.jackson.core.type.TypeReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseCustomerControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_CUSTOMER_TYPE_REFERENCE = new TypeReference<>() { + }; - private IdComparator idComparator = new IdComparator<>(); + ListeningExecutorService executor; private Tenant savedTenant; private User tenantAdmin; @Before public void beforeTest() throws Exception { + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); + loginSysAdmin(); Tenant tenant = new Tenant(); @@ -65,6 +74,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @After public void afterTest() throws Exception { + executor.shutdownNow(); + loginSysAdmin(); doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) @@ -83,11 +94,11 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest savedCustomer.setTitle("My new customer"); doPost("/api/customer", savedCustomer, Customer.class); - Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); + Customer foundCustomer = doGet("/api/customer/" + savedCustomer.getId().getId().toString(), Customer.class); Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle()); - doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) - .andExpect(status().isOk()); + doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) + .andExpect(status().isOk()); } @Test @@ -182,29 +193,30 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest public void testFindCustomers() throws Exception { TenantId tenantId = savedTenant.getId(); - List customers = new ArrayList<>(); + List> futures = new ArrayList<>(135); for (int i = 0; i < 135; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); customer.setTitle("Customer" + i); - customers.add(doPost("/api/customer", customer, Customer.class)); + futures.add(executor.submit(() -> + doPost("/api/customer", customer, Customer.class))); } + List customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - List loadedCustomers = new ArrayList<>(); + List loadedCustomers = new ArrayList<>(135); PageLink pageLink = new PageLink(23); PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink); loadedCustomers.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(customers, idComparator); - Collections.sort(loadedCustomers, idComparator); + assertThat(customers).containsExactlyInAnyOrderElementsOf(loadedCustomers); - Assert.assertEquals(customers, loadedCustomers); + deleteEntitiesAsync("/api/customer/", loadedCustomers, executor).get(TIMEOUT, TimeUnit.SECONDS); } @Test @@ -212,7 +224,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest TenantId tenantId = savedTenant.getId(); String title1 = "Customer title 1"; - List customersTitle1 = new ArrayList<>(); + List> futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); @@ -220,10 +232,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); - customersTitle1.add(doPost("/api/customer", customer, Customer.class)); + futures.add(executor.submit(() -> + doPost("/api/customer", customer, Customer.class))); } + List customersTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + String title2 = "Customer title 2"; - List customersTitle2 = new ArrayList<>(); + futures = new ArrayList<>(175); for (int i = 0; i < 175; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); @@ -231,57 +246,48 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); - customersTitle2.add(doPost("/api/customer", customer, Customer.class)); + futures.add(executor.submit(() -> + doPost("/api/customer", customer, Customer.class))); } + List customersTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + List loadedCustomersTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink); loadedCustomersTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(customersTitle1, idComparator); - Collections.sort(loadedCustomersTitle1, idComparator); - - Assert.assertEquals(customersTitle1, loadedCustomersTitle1); + assertThat(customersTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle1); List loadedCustomersTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink); loadedCustomersTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(customersTitle2, idComparator); - Collections.sort(loadedCustomersTitle2, idComparator); + assertThat(customersTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle2); - Assert.assertEquals(customersTitle2, loadedCustomersTitle2); - - for (Customer customer : loadedCustomersTitle1) { - doDelete("/api/customer/" + customer.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/customer/", loadedCustomersTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4, 0, title1); - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - for (Customer customer : loadedCustomersTitle2) { - doDelete("/api/customer/" + customer.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/customer/", loadedCustomersTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4, 0, title2); - pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java index 27173b3e38..d6e30c644c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java @@ -17,11 +17,17 @@ package org.thingsboard.server.controller; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; @@ -41,22 +47,32 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.model.ModelConstants; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +@Slf4j public abstract class BaseDeviceControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { + }; - private IdComparator idComparator = new IdComparator<>(); + ListeningExecutorService executor; + + List> futures; + PageData pageData; private Tenant savedTenant; private User tenantAdmin; @Before public void beforeTest() throws Exception { + log.debug("beforeTest"); + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); + loginSysAdmin(); Tenant tenant = new Tenant(); @@ -76,10 +92,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @After public void afterTest() throws Exception { + log.debug("afterTest..."); + executor.shutdownNow(); + loginSysAdmin(); - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + doDelete("/api/tenant/" + savedTenant.getId().getId()) .andExpect(status().isOk()); + log.debug("afterTest done"); } @Test @@ -98,7 +118,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(device.getName(), savedDevice.getName()); DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); Assert.assertNotNull(deviceCredentials); Assert.assertNotNull(deviceCredentials.getId()); @@ -110,7 +130,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { savedDevice.setName("My new device"); doPost("/api/device", savedDevice, Device.class); - Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); } @@ -145,7 +165,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setName("My device"); device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); - Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class); Assert.assertNotNull(foundDevice); Assert.assertEquals(savedDevice, foundDevice); } @@ -180,6 +200,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertEquals("typeA", deviceTypes.get(0).getType()); Assert.assertEquals("typeB", deviceTypes.get(1).getType()); Assert.assertEquals("typeC", deviceTypes.get(2).getType()); + + deleteEntitiesAsync("/api/device/", devices, executor).get(TIMEOUT, TimeUnit.SECONDS); } @Test @@ -189,10 +211,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); - doDelete("/api/device/" + savedDevice.getId().getId().toString()) + doDelete("/api/device/" + savedDevice.getId().getId()) .andExpect(status().isOk()); - doGet("/api/device/" + savedDevice.getId().getId().toString()) + doGet("/api/device/" + savedDevice.getId().getId()) .andExpect(status().isNotFound()); } @@ -224,18 +246,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { customer.setTitle("My customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString() - + "/device/" + savedDevice.getId().getId().toString(), Device.class); + Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId() + + "/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId()); - Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId()); Device unassignedDevice = - doDelete("/api/customer/device/" + savedDevice.getId().getId().toString(), Device.class); + doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); - foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); + foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId()); } @@ -246,7 +268,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); doPost("/api/customer/" + Uuids.timeBased().toString() - + "/device/" + savedDevice.getId().getId().toString()) + + "/device/" + savedDevice.getId().getId()) .andExpect(status().isNotFound()); } @@ -279,13 +301,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); - doPost("/api/customer/" + savedCustomer.getId().getId().toString() - + "/device/" + savedDevice.getId().getId().toString()) + doPost("/api/customer/" + savedCustomer.getId().getId() + + "/device/" + savedDevice.getId().getId()) .andExpect(status().isForbidden()); loginSysAdmin(); - doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) + doDelete("/api/tenant/" + savedTenant2.getId().getId()) .andExpect(status().isOk()); } @@ -296,7 +318,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); } @@ -307,7 +329,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsId("access_token"); @@ -315,7 +337,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { .andExpect(status().isOk()); DeviceCredentials foundDeviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); Assert.assertEquals(deviceCredentials, foundDeviceCredentials); } @@ -334,7 +356,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); deviceCredentials.setCredentialsType(null); doPost("/api/device/credentials", deviceCredentials) .andExpect(status().isBadRequest()) @@ -348,7 +370,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); deviceCredentials.setCredentialsId(null); doPost("/api/device/credentials", deviceCredentials) .andExpect(status().isBadRequest()) @@ -362,7 +384,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(Uuids.timeBased())); newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime()); newDeviceCredentials.setDeviceId(deviceCredentials.getDeviceId()); @@ -380,7 +402,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); deviceCredentials.setDeviceId(new DeviceId(Uuids.timeBased())); doPost("/api/device/credentials", deviceCredentials) .andExpect(status().isNotFound()); @@ -388,19 +410,24 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @Test public void testFindTenantDevices() throws Exception { - List devices = new ArrayList<>(); + log.debug("testFindTenantDevices"); + futures = new ArrayList<>(178); for (int i = 0; i < 178; i++) { Device device = new Device(); device.setName("Device" + i); device.setType("default"); - devices.add(doPost("/api/device", device, Device.class)); + futures.add(executor.submit(() -> + doPost("/api/device", device, Device.class))); } - List loadedDevices = new ArrayList<>(); + log.debug("await create devices"); + List devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + + log.debug("start reading"); + List loadedDevices = new ArrayList<>(178); PageLink pageLink = new PageLink(23); - PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + PAGE_DATA_DEVICE_TYPE_REF, pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -408,16 +435,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - Collections.sort(devices, idComparator); - Collections.sort(loadedDevices, idComparator); - - Assert.assertEquals(devices, loadedDevices); + log.debug("asserting"); + assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices); + log.debug("delete devices async"); + deleteEntitiesAsync("/api/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS); + log.debug("done"); } @Test public void testFindTenantDevicesByName() throws Exception { String title1 = "Device title 1"; - List devicesTitle1 = new ArrayList<>(); + + futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -425,10 +454,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - devicesTitle1.add(doPost("/api/device", device, Device.class)); + futures.add(executor.submit(() -> + doPost("/api/device", device, Device.class))); } + List devicesTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + String title2 = "Device title 2"; - List devicesTitle2 = new ArrayList<>(); + futures = new ArrayList<>(75); for (int i = 0; i < 75; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -436,59 +468,50 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - devicesTitle2.add(doPost("/api/device", device, Device.class)); + futures.add(executor.submit(() -> + doPost("/api/device", device, Device.class))); } + List devicesTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - List loadedDevicesTitle1 = new ArrayList<>(); + List loadedDevicesTitle1 = new ArrayList<>(143); PageLink pageLink = new PageLink(15, 0, title1); - PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + PAGE_DATA_DEVICE_TYPE_REF, pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesTitle1, idComparator); - Collections.sort(loadedDevicesTitle1, idComparator); - - Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); + assertThat(devicesTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle1); - List loadedDevicesTitle2 = new ArrayList<>(); + List loadedDevicesTitle2 = new ArrayList<>(75); pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + PAGE_DATA_DEVICE_TYPE_REF, pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesTitle2, idComparator); - Collections.sort(loadedDevicesTitle2, idComparator); + assertThat(devicesTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle2); - Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); + deleteEntitiesAsync("/api/device/", loadedDevicesTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS); - for (Device device : loadedDevicesTitle1) { - doDelete("/api/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + PAGE_DATA_DEVICE_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - for (Device device : loadedDevicesTitle2) { - doDelete("/api/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/device/", loadedDevicesTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS); + pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/tenant/devices?", - new TypeReference>(){}, pageLink); + PAGE_DATA_DEVICE_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -497,7 +520,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { public void testFindTenantDevicesByType() throws Exception { String title1 = "Device title 1"; String type1 = "typeA"; - List devicesType1 = new ArrayList<>(); + futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -505,11 +528,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType(type1); - devicesType1.add(doPost("/api/device", device, Device.class)); + futures.add(executor.submit(() -> + doPost("/api/device", device, Device.class))); + if (i == 0) { + futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time + } } + List devicesType1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + String title2 = "Device title 2"; String type2 = "typeB"; - List devicesType2 = new ArrayList<>(); + futures = new ArrayList<>(75); for (int i = 0; i < 75; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -517,61 +546,54 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType(type2); - devicesType2.add(doPost("/api/device", device, Device.class)); + futures.add(executor.submit(() -> + doPost("/api/device", device, Device.class))); + if (i == 0) { + futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time + } } - List loadedDevicesType1 = new ArrayList<>(); + List devicesType2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + + List loadedDevicesType1 = new ArrayList<>(143); PageLink pageLink = new PageLink(15); - PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1); loadedDevicesType1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesType1, idComparator); - Collections.sort(loadedDevicesType1, idComparator); + assertThat(devicesType1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesType1); - Assert.assertEquals(devicesType1, loadedDevicesType1); - - List loadedDevicesType2 = new ArrayList<>(); + List loadedDevicesType2 = new ArrayList<>(75); pageLink = new PageLink(4); do { pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2); loadedDevicesType2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesType2, idComparator); - Collections.sort(loadedDevicesType2, idComparator); - - Assert.assertEquals(devicesType2, loadedDevicesType2); + assertThat(devicesType2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesType2); - for (Device device : loadedDevicesType1) { - doDelete("/api/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/device/", loadedDevicesType1, executor).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - for (Device device : loadedDevicesType2) { - doDelete("/api/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/device/", loadedDevicesType2, executor).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -583,32 +605,35 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { customer = doPost("/api/customer", customer, Customer.class); CustomerId customerId = customer.getId(); - List devices = new ArrayList<>(); + futures = new ArrayList<>(128); for (int i = 0; i < 128; i++) { Device device = new Device(); device.setName("Device" + i); device.setType("default"); - device = doPost("/api/device", device, Device.class); - devices.add(doPost("/api/customer/" + customerId.getId().toString() - + "/device/" + device.getId().getId().toString(), Device.class)); + ListenableFuture future = executor.submit(() -> doPost("/api/device", device, Device.class)); + futures.add(Futures.transform(future, (dev) -> + doPost("/api/customer/" + customerId.getId() + + "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor())); } - List loadedDevices = new ArrayList<>(); + List devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + + List loadedDevices = new ArrayList<>(128); PageLink pageLink = new PageLink(23); - PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?", + PAGE_DATA_DEVICE_TYPE_REF, pageLink); loadedDevices.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devices, idComparator); - Collections.sort(loadedDevices, idComparator); + assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices); - Assert.assertEquals(devices, loadedDevices); + log.debug("delete devices async"); + deleteEntitiesAsync("/api/customer/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS); + log.debug("done"); } @Test @@ -619,7 +644,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { CustomerId customerId = customer.getId(); String title1 = "Device title 1"; - List devicesTitle1 = new ArrayList<>(); + futures = new ArrayList<>(125); for (int i = 0; i < 125; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -627,12 +652,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - device = doPost("/api/device", device, Device.class); - devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString() - + "/device/" + device.getId().getId().toString(), Device.class)); + ListenableFuture future = executor.submit(() -> doPost("/api/device", device, Device.class)); + futures.add(Futures.transform(future, (dev) -> + doPost("/api/customer/" + customerId.getId() + + "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor())); } + List devicesTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + String title2 = "Device title 2"; - List devicesTitle2 = new ArrayList<>(); + futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -640,61 +668,52 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType("default"); - device = doPost("/api/device", device, Device.class); - devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString() - + "/device/" + device.getId().getId().toString(), Device.class)); + ListenableFuture future = executor.submit(() -> doPost("/api/device", device, Device.class)); + futures.add(Futures.transform(future, (dev) -> + doPost("/api/customer/" + customerId.getId() + + "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor())); } + List devicesTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - List loadedDevicesTitle1 = new ArrayList<>(); + List loadedDevicesTitle1 = new ArrayList<>(125); PageLink pageLink = new PageLink(15, 0, title1); - PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?", + PAGE_DATA_DEVICE_TYPE_REF, pageLink); loadedDevicesTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesTitle1, idComparator); - Collections.sort(loadedDevicesTitle1, idComparator); + assertThat(devicesTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle1); - Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); - - List loadedDevicesTitle2 = new ArrayList<>(); + List loadedDevicesTitle2 = new ArrayList<>(143); pageLink = new PageLink(4, 0, title2); do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?", + PAGE_DATA_DEVICE_TYPE_REF, pageLink); loadedDevicesTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesTitle2, idComparator); - Collections.sort(loadedDevicesTitle2, idComparator); + assertThat(devicesTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle2); - Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); + deleteEntitiesAsync("/api/customer/device/", loadedDevicesTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS); - for (Device device : loadedDevicesTitle1) { - doDelete("/api/customer/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } pageLink = new PageLink(4, 0, title1); - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?", + PAGE_DATA_DEVICE_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - for (Device device : loadedDevicesTitle2) { - doDelete("/api/customer/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/customer/device/", loadedDevicesTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS); + pageLink = new PageLink(4, 0, title2); - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?", + PAGE_DATA_DEVICE_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -708,7 +727,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { String title1 = "Device title 1"; String type1 = "typeC"; - List devicesType1 = new ArrayList<>(); + futures = new ArrayList<>(125); for (int i = 0; i < 125; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -716,13 +735,19 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType(type1); - device = doPost("/api/device", device, Device.class); - devicesType1.add(doPost("/api/customer/" + customerId.getId().toString() - + "/device/" + device.getId().getId().toString(), Device.class)); + ListenableFuture future = executor.submit(() -> doPost("/api/device", device, Device.class)); + futures.add(Futures.transform(future, (dev) -> + doPost("/api/customer/" + customerId.getId() + + "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor())); + if (i == 0) { + futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time + } } + List devicesType1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + String title2 = "Device title 2"; String type2 = "typeD"; - List devicesType2 = new ArrayList<>(); + futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Device device = new Device(); String suffix = RandomStringUtils.randomAlphanumeric(15); @@ -730,63 +755,55 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); device.setName(name); device.setType(type2); - device = doPost("/api/device", device, Device.class); - devicesType2.add(doPost("/api/customer/" + customerId.getId().toString() - + "/device/" + device.getId().getId().toString(), Device.class)); + ListenableFuture future = executor.submit(() -> doPost("/api/device", device, Device.class)); + futures.add(Futures.transform(future, (dev) -> + doPost("/api/customer/" + customerId.getId() + + "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor())); + if (i == 0) { + futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time + } } + List devicesType2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - List loadedDevicesType1 = new ArrayList<>(); + List loadedDevicesType1 = new ArrayList<>(125); PageLink pageLink = new PageLink(15); - PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&", + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1); loadedDevicesType1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesType1, idComparator); - Collections.sort(loadedDevicesType1, idComparator); + assertThat(devicesType1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesType1); - Assert.assertEquals(devicesType1, loadedDevicesType1); - - List loadedDevicesType2 = new ArrayList<>(); + List loadedDevicesType2 = new ArrayList<>(143); pageLink = new PageLink(4); do { - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&", + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2); loadedDevicesType2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(devicesType2, idComparator); - Collections.sort(loadedDevicesType2, idComparator); - - Assert.assertEquals(devicesType2, loadedDevicesType2); + assertThat(devicesType2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesType2); - for (Device device : loadedDevicesType1) { - doDelete("/api/customer/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/customer/device/", loadedDevicesType1, executor).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4); - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type1); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&", + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - for (Device device : loadedDevicesType2) { - doDelete("/api/customer/device/" + device.getId().getId().toString()) - .andExpect(status().isOk()); - } + deleteEntitiesAsync("/api/customer/device/", loadedDevicesType2, executor).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4); - pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", - new TypeReference>(){}, pageLink, type2); + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&", + PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -828,17 +845,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { login("tenant2@thingsboard.org", "testPassword1"); Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class); - doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class, status().isNotFound()); + doGet("/api/device/" + assignedDevice.getId().getId(), Device.class, status().isNotFound()); login("tenant9@thingsboard.org", "testPassword1"); - Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class); + Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId(), Device.class); Assert.assertNotNull(foundDevice1); doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound()); loginSysAdmin(); - doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) + doDelete("/api/tenant/" + savedDifferentTenant.getId().getId()) .andExpect(status().isOk()); } @@ -852,19 +869,19 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); - doPost("/api/edge/" + savedEdge.getId().getId().toString() - + "/device/" + savedDevice.getId().getId().toString(), Device.class); + doPost("/api/edge/" + savedEdge.getId().getId() + + "/device/" + savedDevice.getId().getId(), Device.class); - PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/devices?", - new TypeReference>() {}, new PageLink(100)); + pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?", + PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100)); Assert.assertEquals(1, pageData.getData().size()); - doDelete("/api/edge/" + savedEdge.getId().getId().toString() - + "/device/" + savedDevice.getId().getId().toString(), Device.class); + doDelete("/api/edge/" + savedEdge.getId().getId() + + "/device/" + savedDevice.getId().getId(), Device.class); - pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/devices?", - new TypeReference>() {}, new PageLink(100)); + pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?", + PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100)); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 6e0839b354..143fdce0f3 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; @@ -93,7 +94,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile.getId()); @@ -112,13 +113,13 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void saveDeviceProfileWithViolationOfValidation() throws Exception { - doPost("/api/deviceProfile", this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300), null)) + doPost("/api/deviceProfile", this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300))) .andExpect(statusReason(containsString("length of name must be equal or less than 255"))); } @Test public void testFindDeviceProfileById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertNotNull(foundDeviceProfile); @@ -127,7 +128,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testFindDeviceProfileInfoById() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); Assert.assertNotNull(foundDeviceProfileInfo); @@ -149,9 +150,9 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSetDefaultDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", null, DeviceProfile.class); + DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", DeviceProfile.class); Assert.assertNotNull(defaultDeviceProfile); DeviceProfileInfo foundDefaultDeviceProfile = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class); Assert.assertNotNull(foundDefaultDeviceProfile); @@ -169,19 +170,19 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfileWithSameName() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile"); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such name already exists"))); } @Test public void testSaveDeviceProfileWithSameProvisionDeviceKey() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); deviceProfile.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); - DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2", null); + DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); @@ -190,7 +191,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Ignore @Test public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -205,7 +206,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testChangeDeviceProfileTransportTypeWithExistingDevices() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); device.setName("Test device"); @@ -219,7 +220,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfileWithExistingDevice() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Device device = new Device(); @@ -236,7 +237,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testDeleteDeviceProfile() throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) @@ -257,7 +258,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(pageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -302,7 +303,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController deviceProfiles.addAll(deviceProfilePageData.getData()); for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i, null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -835,19 +836,35 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController "}", "[Transport Configuration] invalid rpc request proto schema provided! Failed to get field descriptor for field: params!"); } + @Test + public void testSaveDeviceProfileWithSendAckOnValidationException() throws Exception { + JsonTransportPayloadConfiguration jsonTransportPayloadConfiguration = new JsonTransportPayloadConfiguration(); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(jsonTransportPayloadConfiguration, true); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertNotNull(savedDeviceProfile); + Assert.assertEquals(savedDeviceProfile.getTransportType(), DeviceTransportType.MQTT); + Assert.assertTrue(savedDeviceProfile.getProfileData().getTransportConfiguration() instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) savedDeviceProfile.getProfileData().getTransportConfiguration(); + Assert.assertTrue(transportConfiguration.isSendAckOnValidationException()); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+ savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); + } + private DeviceProfile testSaveDeviceProfileWithProtoPayloadType(String schema) throws Exception { ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, null, null); - MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); - Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); + Assert.assertNotNull(savedDeviceProfile); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+ savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); return savedDeviceProfile; } private void testSaveDeviceProfileWithInvalidProtoSchema(String schema, String errorMsg) throws Exception { ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, null, null); - MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(errorMsg))); @@ -855,7 +872,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController private void testSaveDeviceProfileWithInvalidRpcRequestProtoSchema(String schema, String errorMsg) throws Exception { ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, schema, null); - MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(errorMsg))); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java index 6bf1a6f1d2..24b2053c67 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java @@ -23,6 +23,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; @@ -54,6 +55,9 @@ import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +@TestPropertySource(properties = { + "edges.enabled=true", +}) public abstract class BaseEdgeControllerTest extends AbstractControllerTest { public static final String EDGE_HOST = "localhost"; 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 0f8b046fb9..ef66270130 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java @@ -22,6 +22,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; @@ -40,6 +41,9 @@ import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@TestPropertySource(properties = { + "edges.enabled=true", +}) @Slf4j public abstract class BaseEdgeEventControllerTest extends AbstractControllerTest { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java index 3b5ec02441..8c692ef8a2 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java @@ -16,10 +16,13 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; @@ -29,6 +32,8 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.DeviceTypeFilter; +import org.thingsboard.server.common.data.query.DynamicValue; +import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.EntityCountQuery; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; @@ -41,11 +46,13 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter; import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.NumericFilterPredicate; +import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.security.Authority; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -299,6 +306,79 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe List deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList()); Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures); + } + + @Test + public void testFindEntityDataByQueryWithDynamicValue() throws Exception { + int numOfDevices = 2; + + for (int i = 0; i < numOfDevices; i++) { + Device device = new Device(); + String name = "Device" + i; + device.setName(name); + device.setType("default"); + device.setLabel("testLabel" + (int) (Math.random() * 1000)); + + Device savedDevice1 = doPost("/api/device?accessToken=" + name, device, Device.class); + JsonNode content = JacksonUtil.toJsonNode("{\"alarmActiveTime\": 1" + i + "}"); + doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice1.getUuidId() + "/SERVER_SCOPE", content) + .andExpect(status().isOk()); + } + JsonNode content = JacksonUtil.toJsonNode("{\"dynamicValue\": 0}"); + doPost("/api/plugins/telemetry/" + EntityType.TENANT.name() + "/" + tenantId.getId() + "/SERVER_SCOPE", content) + .andExpect(status().isOk()); + + + DeviceTypeFilter filter = new DeviceTypeFilter(); + filter.setDeviceType("default"); + filter.setDeviceNameFilter(""); + + KeyFilter highTemperatureFilter = new KeyFilter(); + highTemperatureFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "alarmActiveTime")); + NumericFilterPredicate predicate = new NumericFilterPredicate(); + + DynamicValue dynamicValue = + new DynamicValue<>(DynamicValueSourceType.CURRENT_TENANT, "dynamicValue"); + FilterPredicateValue predicateValue = new FilterPredicateValue<>(0.0, null, dynamicValue); + predicate.setValue(predicateValue); + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperatureFilter.setPredicate(predicate); + + List keyFilters = Collections.singletonList(highTemperatureFilter); + + + EntityDataSortOrder sortOrder = new EntityDataSortOrder( + new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC + ); + EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); + + List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); + List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmActiveTime")); + + EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters); + + Awaitility.await() + .alias("data by query") + .atMost(30, TimeUnit.SECONDS) + .until(() -> { + var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {}); + var loadedEntities = new ArrayList<>(data.getData()); + return loadedEntities.size() == numOfDevices; + }); + + var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {}); + var loadedEntities = new ArrayList<>(data.getData()); + + Assert.assertEquals(numOfDevices, loadedEntities.size()); + + for (int i = 0; i < numOfDevices; i++) { + var entity = loadedEntities.get(i); + String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue(); + String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue(); + + Assert.assertEquals("Device" + i, name); + Assert.assertEquals("1" + i, alarmActiveTime); + } } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java index 07955c0f93..155c768cb0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -17,8 +17,13 @@ package org.thingsboard.server.controller; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttMessage; @@ -27,6 +32,9 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.ResultActions; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityView; @@ -39,70 +47,71 @@ import org.thingsboard.server.common.data.objects.AttributesEntityView; import org.thingsboard.server.common.data.objects.TelemetryEntityView; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.DeviceTypeFilter; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.model.ModelConstants; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +@TestPropertySource(properties = { + "transport.mqtt.enabled=true", + "js.evaluator=mock", +}) @Slf4j public abstract class BaseEntityViewControllerTest extends AbstractControllerTest { + static final TypeReference> PAGE_DATA_ENTITY_VIEW_TYPE_REF = new TypeReference<>() { + }; + static final TypeReference> PAGE_DATA_ENTITY_VIEW_INFO_TYPE_REF = new TypeReference<>() { + }; - private IdComparator idComparator; - private Tenant savedTenant; - private User tenantAdmin; private Device testDevice; private TelemetryEntityView telemetry; + List> deleteFutures = new ArrayList<>(); + ListeningExecutorService executor; + @Before public void beforeTest() throws Exception { - loginSysAdmin(); - idComparator = new IdComparator<>(); + log.debug("beforeTest"); + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); - savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + loginTenantAdmin(); Device device = new Device(); - device.setName("Test device"); + device.setName("Test device 4view"); device.setType("default"); testDevice = doPost("/api/device", device, Device.class); telemetry = new TelemetryEntityView( - Arrays.asList("tsKey1", "tsKey2", "tsKey3"), + List.of("tsKey1", "tsKey2", "tsKey3"), new AttributesEntityView( - Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4"), - Arrays.asList("saKey1", "saKey2", "saKey3", "saKey4"), - Arrays.asList("shKey1", "shKey2", "shKey3", "shKey4"))); + List.of("caKey1", "caKey2", "caKey3", "caKey4"), + List.of("saKey1", "saKey2", "saKey3", "saKey4"), + List.of("shKey1", "shKey2", "shKey3", "shKey4"))); } @After public void afterTest() throws Exception { - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); + executor.shutdownNow(); } @Test @@ -115,22 +124,29 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testSaveEntityView() throws Exception { - EntityView savedView = getNewSavedEntityView("Test entity view"); + String name = "Test entity view"; + EntityView savedView = getNewSavedEntityView(name); Assert.assertNotNull(savedView); Assert.assertNotNull(savedView.getId()); Assert.assertTrue(savedView.getCreatedTime() > 0); - assertEquals(savedTenant.getId(), savedView.getTenantId()); + assertEquals(tenantId, savedView.getTenantId()); Assert.assertNotNull(savedView.getCustomerId()); assertEquals(NULL_UUID, savedView.getCustomerId().getId()); - assertEquals(savedView.getName(), savedView.getName()); + assertEquals(name, savedView.getName()); + + EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); + + assertEquals(savedView, foundEntityView); savedView.setName("New test entity view"); + doPost("/api/entityView", savedView, EntityView.class); - EntityView foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); + foundEntityView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); + + assertEquals(savedView, foundEntityView); - assertEquals(foundEntityView.getName(), savedView.getName()); - assertEquals(foundEntityView.getKeys(), telemetry); + doGet("/api/tenant/entityViews?entityViewName=" + name, EntityView.class, status().isNotFound()); } @Test @@ -221,7 +237,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Customer customer = getNewCustomer("Different customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - login(tenantAdmin.getEmail(), "testPassword1"); + login(TENANT_ADMIN_EMAIL, TENANT_ADMIN_PASSWORD); EntityView savedView = getNewSavedEntityView("Test entity view"); @@ -240,21 +256,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes CustomerId customerId = customer.getId(); String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViewInfos?"; - List views = new ArrayList<>(); + List> viewFutures = new ArrayList<>(128); for (int i = 0; i < 128; i++) { - views.add( + String entityName = "Test entity view " + i; + viewFutures.add(executor.submit(() -> new EntityViewInfo(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/" - + getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class), - customer.getTitle(), customer.isPublic()) - ); + + getNewSavedEntityView(entityName).getId().getId().toString(), EntityView.class), + customer.getTitle(), customer.isPublic()))); } - + List entityViewInfos = Futures.allAsList(viewFutures).get(TIMEOUT, SECONDS); List loadedViews = loadListOfInfo(new PageLink(23), urlTemplate); - Collections.sort(views, idComparator); - Collections.sort(loadedViews, idComparator); - - assertEquals(views, loadedViews); + assertThat(entityViewInfos).containsExactlyInAnyOrderElementsOf(loadedViews); } @Test @@ -263,35 +276,37 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; String name1 = "Entity view name1"; - List namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString() - + "/entityView/"); + List namesOfView1 = Futures.allAsList(fillListByTemplate(125, name1, "/api/customer/" + customerId.getId().toString() + + "/entityView/")).get(TIMEOUT, SECONDS); List loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), urlTemplate); - Collections.sort(namesOfView1, idComparator); - Collections.sort(loadedNamesOfView1, idComparator); - assertEquals(namesOfView1, loadedNamesOfView1); + assertThat(namesOfView1).as(name1).containsExactlyInAnyOrderElementsOf(loadedNamesOfView1); String name2 = "Entity view name2"; - List NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString() - + "/entityView/"); + List namesOfView2 = Futures.allAsList(fillListByTemplate(143, name2, "/api/customer/" + customerId.getId().toString() + + "/entityView/")).get(TIMEOUT, SECONDS); List loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), urlTemplate); - Collections.sort(NamesOfView2, idComparator); - Collections.sort(loadedNamesOfView2, idComparator); - assertEquals(NamesOfView2, loadedNamesOfView2); + assertThat(namesOfView2).as(name2).containsExactlyInAnyOrderElementsOf(loadedNamesOfView2); + deleteFutures.clear(); for (EntityView view : loadedNamesOfView1) { - doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + deleteFutures.add(executor.submit(() -> + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()))); } - PageData pageData = doGetTypedWithPageLink(urlTemplate, - new TypeReference>() { - }, new PageLink(4, 0, name1)); + Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS); + + PageData pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF, + new PageLink(4, 0, name1)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); + deleteFutures.clear(); for (EntityView view : loadedNamesOfView2) { - doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + deleteFutures.add(executor.submit(() -> + doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()))); } - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { - }, + Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS); + + pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(4, 0, name2)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); @@ -299,49 +314,51 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testGetTenantEntityViews() throws Exception { - - List views = new ArrayList<>(); + List> entityViewInfoFutures = new ArrayList<>(178); for (int i = 0; i < 178; i++) { - views.add(new EntityViewInfo(getNewSavedEntityView("Test entity view" + i), null, false)); + ListenableFuture entityViewFuture = getNewSavedEntityViewAsync("Test entity view" + i); + entityViewInfoFutures.add(Futures.transform(entityViewFuture, + view -> new EntityViewInfo(view, null, false), + MoreExecutors.directExecutor())); } + List entityViewInfos = Futures.allAsList(entityViewInfoFutures).get(TIMEOUT, SECONDS); List loadedViews = loadListOfInfo(new PageLink(23), "/api/tenant/entityViewInfos?"); - - Collections.sort(views, idComparator); - Collections.sort(loadedViews, idComparator); - - assertEquals(views, loadedViews); + assertThat(entityViewInfos).containsExactlyInAnyOrderElementsOf(loadedViews); } @Test public void testGetTenantEntityViewsByName() throws Exception { String name1 = "Entity view name1"; - List namesOfView1 = fillListOf(143, name1); - List loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), "/api/tenant/entityViews?"); - Collections.sort(namesOfView1, idComparator); - Collections.sort(loadedNamesOfView1, idComparator); - assertEquals(namesOfView1, loadedNamesOfView1); + List namesOfView1 = Futures.allAsList(fillListOf(17, name1)).get(TIMEOUT, SECONDS); + List loadedNamesOfView1 = loadListOf(new PageLink(5, 0, name1), "/api/tenant/entityViews?"); + assertThat(namesOfView1).as(name1).containsExactlyInAnyOrderElementsOf(loadedNamesOfView1); String name2 = "Entity view name2"; - List NamesOfView2 = fillListOf(75, name2); + List namesOfView2 = Futures.allAsList(fillListOf(15, name2)).get(TIMEOUT, SECONDS); + ; List loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), "/api/tenant/entityViews?"); - Collections.sort(NamesOfView2, idComparator); - Collections.sort(loadedNamesOfView2, idComparator); - assertEquals(NamesOfView2, loadedNamesOfView2); + assertThat(namesOfView2).as(name2).containsExactlyInAnyOrderElementsOf(loadedNamesOfView2); + deleteFutures.clear(); for (EntityView view : loadedNamesOfView1) { - doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + deleteFutures.add(executor.submit(() -> + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()))); } - PageData pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", - new TypeReference>() { - }, new PageLink(4, 0, name1)); + Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS); + + PageData pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", PAGE_DATA_ENTITY_VIEW_TYPE_REF, + new PageLink(4, 0, name1)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); + deleteFutures.clear(); for (EntityView view : loadedNamesOfView2) { - doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); + deleteFutures.add(executor.submit(() -> + doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()))); } - pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference>() { - }, + Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS); + + pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(4, 0, name2)); Assert.assertFalse(pageData.hasNext()); assertEquals(0, pageData.getData().size()); @@ -349,20 +366,21 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testTheCopyOfAttrsIntoTSForTheView() throws Exception { + Set expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4"); Set actualAttributesSet = - getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}"); - - Set expectedActualAttributesSet = - new HashSet<>(Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4")); - assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet)); - + getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet); + log.debug("got correct actualAttributesSet, saving new entity view..."); EntityView savedView = getNewSavedEntityView("Test entity view"); - Thread.sleep(1000); - - List> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + - "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); + log.debug("fetching entity view telemetry..."); + List> values = await("telemetry/ENTITY_VIEW") + .atMost(TIMEOUT, SECONDS) + .until(() -> doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + + "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() { + }), + x -> x.size() >= expectedActualAttributesSet.size()); + log.debug("asserting..."); assertEquals("value1", getValue(values, "caKey1")); assertEquals(true, getValue(values, "caKey2")); assertEquals(42.0, getValue(values, "caKey3")); @@ -371,18 +389,17 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception { + Set expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4"); Set actualAttributesSet = - getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}"); - - Set expectedActualAttributesSet = new HashSet<>(Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4")); - assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet)); + getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet); List> valueTelemetryOfDevices = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() + - "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); + "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() { + }); EntityView view = new EntityView(); view.setEntityId(testDevice.getId()); - view.setTenantId(savedTenant.getId()); + view.setTenantId(tenantId); view.setName("Test entity view"); view.setType("default"); view.setKeys(telemetry); @@ -390,28 +407,49 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10); EntityView savedView = doPost("/api/entityView", view, EntityView.class); - Thread.sleep(1000); - List> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + - "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); + "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() { + }); assertEquals(0, values.size()); } @Test public void testGetTelemetryWhenEntityViewTimeRangeInsideTimestampRange() throws Exception { - uploadTelemetry("{\"tsKey1\":\"value1\", \"tsKey2\":true, \"tsKey3\":40.0}"); - Thread.sleep(1000); + DeviceTypeFilter dtf = new DeviceTypeFilter(testDevice.getType(), testDevice.getName()); + List tsKeys = List.of("tsKey1", "tsKey2", "tsKey3"); + + DeviceCredentials deviceCredentials = doGet("/api/device/" + testDevice.getId().getId() + "/credentials", DeviceCredentials.class); + assertEquals(testDevice.getId(), deviceCredentials.getDeviceId()); + String accessToken = deviceCredentials.getCredentialsId(); + assertNotNull(accessToken); + + long now = System.currentTimeMillis(); + getWsClient().subscribeTsUpdate(tsKeys, now, TimeUnit.HOURS.toMillis(1), dtf); + + getWsClient().registerWaitForUpdate(); + uploadTelemetry("{\"tsKey1\":\"value1\", \"tsKey2\":true, \"tsKey3\":40.0}", accessToken); + getWsClient().waitForUpdate(); + long startTimeMs = System.currentTimeMillis(); - uploadTelemetry("{\"tsKey1\":\"value2\", \"tsKey2\":false, \"tsKey3\":80.0}"); - Thread.sleep(1000); - uploadTelemetry("{\"tsKey1\":\"value3\", \"tsKey2\":false, \"tsKey3\":120.0}"); + + getWsClient().registerWaitForUpdate(); + uploadTelemetry("{\"tsKey1\":\"value2\", \"tsKey2\":false, \"tsKey3\":80.0}", accessToken); + getWsClient().waitForUpdate(); + + Thread.sleep(3); + + getWsClient().registerWaitForUpdate(); + uploadTelemetry("{\"tsKey1\":\"value3\", \"tsKey2\":false, \"tsKey3\":120.0}", accessToken); + getWsClient().waitForUpdate(); + long endTimeMs = System.currentTimeMillis(); - uploadTelemetry("{\"tsKey1\":\"value4\", \"tsKey2\":true, \"tsKey3\":160.0}"); + getWsClient().registerWaitForUpdate(); + uploadTelemetry("{\"tsKey1\":\"value4\", \"tsKey2\":true, \"tsKey3\":160.0}", accessToken); + getWsClient().waitForUpdate(); String deviceId = testDevice.getId().getId().toString(); Set keys = getTelemetryKeys("DEVICE", deviceId); - Thread.sleep(1000); EntityView view = createEntityView("Test entity view", startTimeMs, endTimeMs); EntityView savedView = doPost("/api/entityView", view, EntityView.class); @@ -428,14 +466,8 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Assert.assertEquals(1, actualValues.get("tsKey3").size()); } - private void uploadTelemetry(String strKvs) throws Exception { + private void uploadTelemetry(String strKvs, String accessToken) throws Exception { String viewDeviceId = testDevice.getId().getId().toString(); - DeviceCredentials deviceCredentials = - doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class); - assertEquals(testDevice.getId(), deviceCredentials.getDeviceId()); - - String accessToken = deviceCredentials.getCredentialsId(); - assertNotNull(accessToken); String clientId = MqttAsyncClient.generateClientId(); MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence()); @@ -443,35 +475,45 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes MqttConnectOptions options = new MqttConnectOptions(); options.setUserName(accessToken); client.connect(options); - awaitConnected(client, TimeUnit.SECONDS.toMillis(30)); + awaitConnected(client, SECONDS.toMillis(30)); MqttMessage message = new MqttMessage(); message.setPayload(strKvs.getBytes()); - client.publish("v1/devices/me/telemetry", message); - Thread.sleep(1000); + IMqttDeliveryToken token = client.publish("v1/devices/me/telemetry", message); + await("mqtt ack").pollInterval(5, MILLISECONDS).atMost(TIMEOUT, SECONDS).until(() -> token.getMessage() == null); client.disconnect(); } private void awaitConnected(MqttAsyncClient client, long ms) throws InterruptedException { - long start = System.currentTimeMillis(); - while (!client.isConnected()) { - Thread.sleep(100); - if (start + ms < System.currentTimeMillis()) { - throw new RuntimeException("Client is not connected!"); - } - } + await("awaitConnected").pollInterval(5, MILLISECONDS).atMost(TIMEOUT, SECONDS) + .until(client::isConnected); } private Set getTelemetryKeys(String type, String id) throws Exception { - return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + "/keys/timeseries", new TypeReference<>() {})); + return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + "/keys/timeseries", new TypeReference<>() { + })); + } + + private Set getAttributeKeys(String type, String id) throws Exception { + return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + "/keys/attributes", new TypeReference<>() { + })); } private Map>> getTelemetryValues(String type, String id, Set keys, Long startTs, Long endTs) throws Exception { return doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + - "/values/timeseries?keys=" + String.join(",", keys) + "&startTs=" + startTs + "&endTs=" + endTs, new TypeReference<>() {}); + "/values/timeseries?keys=" + String.join(",", keys) + "&startTs=" + startTs + "&endTs=" + endTs, new TypeReference<>() { + }); } - private Set getAttributesByKeys(String stringKV) throws Exception { + private Set getAttributesByKeys(String stringKV, Set expectedKeySet) throws Exception { + DeviceTypeFilter dtf = new DeviceTypeFilter(testDevice.getType(), testDevice.getName()); + List keysToSubscribe = expectedKeySet.stream() + .map(key -> new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, key)) + .collect(Collectors.toList()); + + getWsClient().subscribeLatestUpdate(keysToSubscribe, dtf); + String viewDeviceId = testDevice.getId().getId().toString(); + log.debug("deviceid {}", viewDeviceId); DeviceCredentials deviceCredentials = doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class); assertEquals(testDevice.getId(), deviceCredentials.getDeviceId()); @@ -479,20 +521,24 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes String accessToken = deviceCredentials.getCredentialsId(); assertNotNull(accessToken); + log.debug("creating mqtt client..."); String clientId = MqttAsyncClient.generateClientId(); MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence()); MqttConnectOptions options = new MqttConnectOptions(); options.setUserName(accessToken); client.connect(options); - awaitConnected(client, TimeUnit.SECONDS.toMillis(30)); - + awaitConnected(client, SECONDS.toMillis(30)); + log.debug("mqtt connected..."); MqttMessage message = new MqttMessage(); message.setPayload((stringKV).getBytes()); - client.publish("v1/devices/me/attributes", message); - Thread.sleep(1000); - client.disconnect(); - return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", new TypeReference<>() {})); + getWsClient().registerWaitForUpdate(); + IMqttDeliveryToken token = client.publish("v1/devices/me/attributes", message); + log.debug("publish token.message {}", token.getMessage()); + await("mqtt ack").pollInterval(5, MILLISECONDS).atMost(TIMEOUT, SECONDS).until(() -> token.getMessage() == null); + log.debug("token.message delivered {}", token.getMessage()); + assertThat(getWsClient().waitForUpdate()).as("ws update received").isNotBlank(); + return getAttributeKeys("DEVICE", viewDeviceId); } private Object getValue(List> values, String stringValue) { @@ -502,15 +548,19 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes .findFirst().get().get("value"); } - private EntityView getNewSavedEntityView(String name) throws Exception { + private EntityView getNewSavedEntityView(String name) { EntityView view = createEntityView(name, 0, 0); return doPost("/api/entityView", view, EntityView.class); } + private ListenableFuture getNewSavedEntityViewAsync(String name) { + return executor.submit(() -> getNewSavedEntityView(name)); + } + private EntityView createEntityView(String name, long startTimeMs, long endTimeMs) { EntityView view = new EntityView(); view.setEntityId(testDevice.getId()); - view.setTenantId(savedTenant.getId()); + view.setTenantId(tenantId); view.setName(name); view.setType("default"); view.setKeys(telemetry); @@ -531,33 +581,41 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes return tenant; } - private List fillListOf(int limit, String partOfName, String urlTemplate) throws Exception { - List views = new ArrayList<>(); - for (EntityView view : fillListOf(limit, partOfName)) { - views.add(doPost(urlTemplate + view.getId().getId().toString(), EntityView.class)); + private List> fillListByTemplate(int limit, String partOfName, String urlTemplate) { + List> futures = new ArrayList<>(limit); + for (ListenableFuture viewFuture : fillListOf(limit, partOfName)) { + futures.add(Futures.transform(viewFuture, view -> + doPost(urlTemplate + view.getId().getId().toString(), EntityView.class), + MoreExecutors.directExecutor())); } - return views; + return futures; } - private List fillListOf(int limit, String partOfName) throws Exception { - List viewNames = new ArrayList<>(); + private List> fillListOf(int limit, String partOfName) { + List> viewNameFutures = new ArrayList<>(limit); for (int i = 0; i < limit; i++) { - String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15); - fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase(); - EntityView view = getNewSavedEntityView(fullName); - Customer customer = getNewCustomer("Test customer " + String.valueOf(Math.random())); - view.setCustomerId(doPost("/api/customer", customer, Customer.class).getId()); - viewNames.add(doPost("/api/entityView", view, EntityView.class)); + boolean even = i % 2 == 0; + ListenableFuture customerFuture = executor.submit(() -> { + Customer customer = getNewCustomer("Test customer " + Math.random()); + return doPost("/api/customer", customer, Customer.class).getId(); + }); + + viewNameFutures.add(Futures.transform(customerFuture, customerId -> { + String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15); + fullName = even ? fullName.toLowerCase() : fullName.toUpperCase(); + EntityView view = getNewSavedEntityView(fullName); + view.setCustomerId(customerId); + return doPost("/api/entityView", view, EntityView.class); + }, MoreExecutors.directExecutor())); } - return viewNames; + return viewNameFutures; } private List loadListOf(PageLink pageLink, String urlTemplate) throws Exception { List loadedItems = new ArrayList<>(); PageData pageData; do { - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { - }, pageLink); + pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF, pageLink); loadedItems.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -571,8 +629,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes List loadedItems = new ArrayList<>(); PageData pageData; do { - pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference>() { - }, pageLink); + pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_INFO_TYPE_REF, pageLink); loadedItems.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -596,7 +653,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/entityViews?", - new TypeReference>() {}, new PageLink(100)); + PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(100)); Assert.assertEquals(1, pageData.getData().size()); @@ -604,7 +661,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/entityViews?", - new TypeReference>() {}, new PageLink(100)); + PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(100)); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java index 38fe065ea9..cbc00b0987 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java @@ -79,7 +79,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); deviceProfileId = savedDeviceProfile.getId(); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java index 21905a12c6..1ae3f497e9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java @@ -16,24 +16,55 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; +import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.ResultActions; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@TestPropertySource(properties = { + "js.evaluator=mock", +}) +@Slf4j public abstract class BaseTenantControllerTest extends AbstractControllerTest { - - private IdComparator idComparator = new IdComparator<>(); + + static final TypeReference> PAGE_DATA_TENANT_TYPE_REF = new TypeReference<>() { + }; + static final TypeReference> PAGE_DATA_TENANT_INFO_TYPE_REF = new TypeReference<>() { + }; + + ListeningExecutorService executor; + + @Before + public void setUp() throws Exception { + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); + } + + @After + public void tearDown() throws Exception { + executor.shutdownNow(); + } @Test public void testSaveTenant() throws Exception { @@ -47,10 +78,10 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { Assert.assertEquals(tenant.getTitle(), savedTenant.getTitle()); savedTenant.setTitle("My new tenant"); doPost("/api/tenant", savedTenant, Tenant.class); - Tenant foundTenant = doGet("/api/tenant/"+savedTenant.getId().getId().toString(), Tenant.class); + Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class); Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle()); - doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); } @Test @@ -60,18 +91,18 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { tenant.setTitle(RandomStringUtils.randomAlphanumeric(300)); doPost("/api/tenant", tenant).andExpect(statusReason(containsString("length of title must be equal or less than 255"))); } - + @Test public void testFindTenantById() throws Exception { loginSysAdmin(); Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Tenant foundTenant = doGet("/api/tenant/"+savedTenant.getId().getId().toString(), Tenant.class); + Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class); Assert.assertNotNull(foundTenant); Assert.assertEquals(savedTenant, foundTenant); - doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); } @Test @@ -80,22 +111,22 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); - TenantInfo foundTenant = doGet("/api/tenant/info/"+savedTenant.getId().getId().toString(), TenantInfo.class); + TenantInfo foundTenant = doGet("/api/tenant/info/" + savedTenant.getId().getId().toString(), TenantInfo.class); Assert.assertNotNull(foundTenant); Assert.assertEquals(new TenantInfo(savedTenant, "Default"), foundTenant); - doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } - + @Test public void testSaveTenantWithEmptyTitle() throws Exception { loginSysAdmin(); Tenant tenant = new Tenant(); doPost("/api/tenant", tenant) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Tenant title should be specified"))); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Tenant title should be specified"))); } - + @Test public void testSaveTenantWithInvalidEmail() throws Exception { loginSysAdmin(); @@ -103,140 +134,147 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { tenant.setTitle("My tenant"); tenant.setEmail("invalid@mail"); doPost("/api/tenant", tenant) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Invalid email address format"))); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Invalid email address format"))); } - + @Test public void testDeleteTenant() throws Exception { loginSysAdmin(); Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); - doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); - doGet("/api/tenant/"+savedTenant.getId().getId().toString()) - .andExpect(status().isNotFound()); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + doGet("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isNotFound()); } - + @Test public void testFindTenants() throws Exception { loginSysAdmin(); List tenants = new ArrayList<>(); PageLink pageLink = new PageLink(17); - PageData pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + PageData pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getData().size()); tenants.addAll(pageData.getData()); - for (int i=0;i<56;i++) { + List> createFutures = new ArrayList<>(56); + for (int i = 0; i < 56; i++) { Tenant tenant = new Tenant(); - tenant.setTitle("Tenant"+i); - tenants.add(doPost("/api/tenant", tenant, Tenant.class)); + tenant.setTitle("Tenant" + i); + createFutures.add(executor.submit(() -> + doPost("/api/tenant", tenant, Tenant.class))); } - + tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS)); + List loadedTenants = new ArrayList<>(); pageLink = new PageLink(17); do { - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); loadedTenants.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - - Collections.sort(tenants, idComparator); - Collections.sort(loadedTenants, idComparator); - - Assert.assertEquals(tenants, loadedTenants); - - for (Tenant tenant : loadedTenants) { - if (!tenant.getTitle().equals(TEST_TENANT_NAME)) { - doDelete("/api/tenant/"+tenant.getId().getId().toString()) - .andExpect(status().isOk()); - } - } - + + assertThat(tenants).containsExactlyInAnyOrderElementsOf(loadedTenants); + + deleteEntitiesAsync("/api/tenant/", loadedTenants.stream() + .filter((t) -> !TEST_TENANT_NAME.equals(t.getTitle())) + .collect(Collectors.toList()), executor).get(TIMEOUT, TimeUnit.SECONDS); + pageLink = new PageLink(17); - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getData().size()); } - + @Test public void testFindTenantsByTitle() throws Exception { + log.debug("login sys admin"); loginSysAdmin(); + log.debug("test started"); String title1 = "Tenant title 1"; - List tenantsTitle1 = new ArrayList<>(); - for (int i=0;i<134;i++) { + List> createFutures = new ArrayList<>(134); + for (int i = 0; i < 134; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); - String title = title1+suffix; + String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); - tenantsTitle1.add(doPost("/api/tenant", tenant, Tenant.class)); + createFutures.add(executor.submit(() -> + doPost("/api/tenant", tenant, Tenant.class))); } + + List tenantsTitle1 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS); + log.debug("saved '{}', qty {}", title1, tenantsTitle1.size()); + String title2 = "Tenant title 2"; - List tenantsTitle2 = new ArrayList<>(); - for (int i=0;i<127;i++) { + createFutures = new ArrayList<>(127); + for (int i = 0; i < 127; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); - String title = title2+suffix; + String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); + String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); - tenantsTitle2.add(doPost("/api/tenant", tenant, Tenant.class)); + createFutures.add(executor.submit(() -> + doPost("/api/tenant", tenant, Tenant.class))); } - - List loadedTenantsTitle1 = new ArrayList<>(); + + List tenantsTitle2 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS); + log.debug("saved '{}', qty {}", title2, tenantsTitle2.size()); + + List loadedTenantsTitle1 = new ArrayList<>(134); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); loadedTenantsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - - Collections.sort(tenantsTitle1, idComparator); - Collections.sort(loadedTenantsTitle1, idComparator); - - Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1); - - List loadedTenantsTitle2 = new ArrayList<>(); + + log.debug("found by name '{}', step 15 {}", title1, loadedTenantsTitle1.size()); + + assertThat(tenantsTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedTenantsTitle1); + log.debug("asserted"); + + List loadedTenantsTitle2 = new ArrayList<>(127); pageLink = new PageLink(4, 0, title2); do { - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); loadedTenantsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(tenantsTitle2, idComparator); - Collections.sort(loadedTenantsTitle2, idComparator); - - Assert.assertEquals(tenantsTitle2, loadedTenantsTitle2); + log.debug("found by name '{}', step 4 {}", title1, loadedTenantsTitle2.size()); + assertThat(tenantsTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedTenantsTitle2); + log.debug("asserted"); + + + deleteEntitiesAsync("/api/tenant/", loadedTenantsTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS); + log.debug("deleted '{}', size {}", title1, loadedTenantsTitle1.size()); - for (Tenant tenant : loadedTenantsTitle1) { - doDelete("/api/tenant/"+tenant.getId().getId().toString()) - .andExpect(status().isOk()); - } - pageLink = new PageLink(4, 0, title1); - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - - for (Tenant tenant : loadedTenantsTitle2) { - doDelete("/api/tenant/"+tenant.getId().getId().toString()) - .andExpect(status().isOk()); - } - + + log.debug("tried to search another '{}', step 4", title1); + + deleteEntitiesAsync("/api/tenant/", loadedTenantsTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS); + log.debug("deleted '{}', size {}", title2, loadedTenantsTitle2.size()); + pageLink = new PageLink(4, 0, title2); - pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); + log.debug("tried to search another '{}', step 4", title2); } @Test @@ -244,42 +282,50 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { loginSysAdmin(); List tenants = new ArrayList<>(); PageLink pageLink = new PageLink(17); - PageData pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference>(){}, pageLink); + PageData pageData = doGetTypedWithPageLink("/api/tenantInfos?", PAGE_DATA_TENANT_INFO_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getData().size()); tenants.addAll(pageData.getData()); - for (int i=0;i<56;i++) { + List> createFutures = new ArrayList<>(56); + for (int i = 0; i < 56; i++) { Tenant tenant = new Tenant(); - tenant.setTitle("Tenant"+i); - tenants.add(new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default")); + tenant.setTitle("Tenant" + i); + createFutures.add(executor.submit(() -> + new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default"))); } + tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS)); List loadedTenants = new ArrayList<>(); pageLink = new PageLink(17); do { - pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenantInfos?", PAGE_DATA_TENANT_INFO_TYPE_REF, pageLink); loadedTenants.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(tenants, idComparator); - Collections.sort(loadedTenants, idComparator); + assertThat(tenants).containsExactlyInAnyOrderElementsOf(loadedTenants); - Assert.assertEquals(tenants, loadedTenants); - - for (TenantInfo tenant : loadedTenants) { - if (!tenant.getTitle().equals(TEST_TENANT_NAME)) { - doDelete("/api/tenant/"+tenant.getId().getId().toString()) - .andExpect(status().isOk()); - } - } + deleteEntitiesAsync("/api/tenant/", loadedTenants.stream() + .filter((t) -> !TEST_TENANT_NAME.equals(t.getTitle())) + .collect(Collectors.toList()), executor).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(17); - pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenantInfos?", PAGE_DATA_TENANT_INFO_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getData().size()); } + + ListenableFuture> deleteTenantsAsync(String urlTemplate, List tenants) { + List> futures = new ArrayList<>(tenants.size()); + for (Tenant device : tenants) { + futures.add(executor.submit(() -> + doDelete(urlTemplate + device.getId().getId()) + .andExpect(status().isOk()))); + } + return Futures.allAsList(futures); + } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java index 774848715e..1867577c23 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java @@ -111,7 +111,7 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController loginSysAdmin(); TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile 1"); TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); - TenantProfile defaultTenantProfile = doPost("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString()+"/default", null, TenantProfile.class); + TenantProfile defaultTenantProfile = doPost("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString()+"/default", TenantProfile.class); Assert.assertNotNull(defaultTenantProfile); EntityInfo foundDefaultTenantProfile = doGet("/api/tenantProfileInfo/default", EntityInfo.class); Assert.assertNotNull(foundDefaultTenantProfile); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java index 15c1c8d3ac..90d6ac7b9d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java @@ -24,10 +24,6 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -46,19 +42,12 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.TsValue; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; -import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; -import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -68,77 +57,38 @@ import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { - - private Tenant savedTenant; - private User tenantAdmin; - private TbTestWebSocketClient wsClient; - +public abstract class BaseWebsocketApiTest extends AbstractControllerTest { @Autowired private TelemetrySubscriptionService tsService; - @Before - public void beforeTest() throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); + Device device; + DeviceTypeFilter dtf; - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + @Before + public void setUp() throws Exception { + loginTenantAdmin(); - wsClient = buildAndConnectWebSocketClient(); + device = new Device(); + device.setName("Device"); + device.setType("default"); + device.setLabel("testLabel" + (int) (Math.random() * 1000)); + device = doPost("/api/device", device, Device.class); + dtf = new DeviceTypeFilter(device.getType(), device.getName()); } @After - public void afterTest() throws Exception { - wsClient.close(); - - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + public void tearDown() throws Exception { + doDelete("/api/device/" + device.getId().getId()) .andExpect(status().isOk()); } @Test public void testEntityDataHistoryWsCmd() throws Exception { - Device device = new Device(); - device.setName("Device"); - device.setType("default"); - device.setLabel("testLabel" + (int) (Math.random() * 1000)); - device = doPost("/api/device", device, Device.class); - + List keys = List.of("temperature"); long now = System.currentTimeMillis(); - DeviceTypeFilter dtf = new DeviceTypeFilter(); - dtf.setDeviceNameFilter("D"); - dtf.setDeviceType("default"); - EntityDataQuery edq = new EntityDataQuery(dtf, - new EntityDataPageLink(1, 0, null, null), - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - EntityHistoryCmd historyCmd = new EntityHistoryCmd(); - historyCmd.setKeys(Arrays.asList("temperature")); - historyCmd.setAgg(Aggregation.NONE); - historyCmd.setLimit(1000); - historyCmd.setStartTs(now - TimeUnit.HOURS.toMillis(1)); - historyCmd.setEndTs(now); - EntityDataCmd cmd = new EntityDataCmd(1, edq, historyCmd, null, null); - - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - wsClient.send(mapper.writeValueAsString(wrapper)); - String msg = wsClient.waitForReply(); - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + EntityDataUpdate update = getWsClient().sendHistoryCmd(keys, now, TimeUnit.HOURS.toMillis(1), dtf); + Assert.assertEquals(1, update.getCmdId()); PageData pageData = update.getData(); Assert.assertNotNull(pageData); @@ -154,9 +104,8 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { sendTelemetry(device, tsData); Thread.sleep(100); - wsClient.send(mapper.writeValueAsString(wrapper)); - msg = wsClient.waitForReply(); - update = mapper.readValue(msg, EntityDataUpdate.class); + update = getWsClient().sendHistoryCmd(keys, now, TimeUnit.HOURS.toMillis(1), dtf); + Assert.assertEquals(1, update.getCmdId()); List dataList = update.getUpdate(); Assert.assertNotNull(dataList); @@ -171,41 +120,16 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { @Test public void testEntityDataTimeSeriesWsCmd() throws Exception { - Device device = new Device(); - device.setName("Device"); - device.setType("default"); - device.setLabel("testLabel" + (int) (Math.random() * 1000)); - device = doPost("/api/device", device, Device.class); - long now = System.currentTimeMillis(); - DeviceTypeFilter dtf = new DeviceTypeFilter(); - dtf.setDeviceNameFilter("D"); - dtf.setDeviceType("default"); - EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null); + EntityDataUpdate update = getWsClient().sendEntityDataQuery(dtf); - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - wsClient.send(mapper.writeValueAsString(wrapper)); - String msg = wsClient.waitForReply(); - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); PageData pageData = update.getData(); Assert.assertNotNull(pageData); Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); - TimeSeriesCmd tsCmd = new TimeSeriesCmd(); - tsCmd.setKeys(Arrays.asList("temperature")); - tsCmd.setAgg(Aggregation.NONE); - tsCmd.setLimit(1000); - tsCmd.setStartTs(now - TimeUnit.HOURS.toMillis(1)); - tsCmd.setTimeWindow(TimeUnit.HOURS.toMillis(1)); - TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 43L)); TsKvEntry dataPoint3 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(3), new LongDataEntry("temperature", 44L)); @@ -214,12 +138,7 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { sendTelemetry(device, tsData); Thread.sleep(100); - cmd = new EntityDataCmd(1, null, null, null, tsCmd); - wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - wsClient.send(mapper.writeValueAsString(wrapper)); - msg = wsClient.waitForReply(); - update = mapper.readValue(msg, EntityDataUpdate.class); + update = getWsClient().subscribeTsUpdate(List.of("temperature"), now, TimeUnit.HOURS.toMillis(1)); Assert.assertEquals(1, update.getCmdId()); List listData = update.getUpdate(); Assert.assertNotNull(listData); @@ -233,10 +152,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { now = System.currentTimeMillis(); TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L)); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); Thread.sleep(100); sendTelemetry(device, Arrays.asList(dataPoint4)); - msg = wsClient.waitForUpdate(); + String msg = getWsClient().waitForUpdate(); update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); @@ -252,44 +171,26 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { @Test public void testEntityCountWsCmd() throws Exception { - Device device = new Device(); - device.setName("Device"); - device.setType("default"); - device.setLabel("testLabel" + (int) (Math.random() * 1000)); - device = doPost("/api/device", device, Device.class); - AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 42L)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Collections.singletonList(dataPoint1)); - DeviceTypeFilter dtf1 = new DeviceTypeFilter(); - dtf1.setDeviceNameFilter("D"); - dtf1.setDeviceType("default"); - EntityCountQuery edq1 = new EntityCountQuery(dtf1, Collections.emptyList()); - + EntityCountQuery edq1 = new EntityCountQuery(dtf, Collections.emptyList()); EntityCountCmd cmd1 = new EntityCountCmd(1, edq1); - TelemetryPluginCmdsWrapper wrapper1 = new TelemetryPluginCmdsWrapper(); - wrapper1.setEntityCountCmds(Collections.singletonList(cmd1)); + getWsClient().send(cmd1); - wsClient.send(mapper.writeValueAsString(wrapper1)); - String msg1 = wsClient.waitForReply(); - EntityCountUpdate update1 = mapper.readValue(msg1, EntityCountUpdate.class); + EntityCountUpdate update1 = getWsClient().parseCountReply(getWsClient().waitForReply()); Assert.assertEquals(1, update1.getCmdId()); Assert.assertEquals(1, update1.getCount()); - DeviceTypeFilter dtf2 = new DeviceTypeFilter(); - dtf2.setDeviceNameFilter("D"); - dtf2.setDeviceType("non-existing-device-type"); + DeviceTypeFilter dtf2 = new DeviceTypeFilter("non-existing-device-type", "D"); EntityCountQuery edq2 = new EntityCountQuery(dtf2, Collections.emptyList()); - EntityCountCmd cmd2 = new EntityCountCmd(2, edq2); - TelemetryPluginCmdsWrapper wrapper2 = new TelemetryPluginCmdsWrapper(); - wrapper2.setEntityCountCmds(Collections.singletonList(cmd2)); - wsClient.send(mapper.writeValueAsString(wrapper2)); + getWsClient().send(cmd2); - String msg2 = wsClient.waitForReply(); - EntityCountUpdate update2 = mapper.readValue(msg2, EntityCountUpdate.class); + String msg2 = getWsClient().waitForReply(); + EntityCountUpdate update2 = getWsClient().parseCountReply(getWsClient().waitForReply()); Assert.assertEquals(2, update2.getCmdId()); Assert.assertEquals(0, update2.getCount()); @@ -301,19 +202,12 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { highTemperatureFilter.setPredicate(predicate); highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC); - DeviceTypeFilter dtf3 = new DeviceTypeFilter(); - dtf3.setDeviceNameFilter("D"); - dtf3.setDeviceType("default"); + DeviceTypeFilter dtf3 = new DeviceTypeFilter("default", "D"); EntityCountQuery edq3 = new EntityCountQuery(dtf3, Collections.singletonList(highTemperatureFilter)); - EntityCountCmd cmd3 = new EntityCountCmd(3, edq3); + getWsClient().send(cmd3); - TelemetryPluginCmdsWrapper wrapper3 = new TelemetryPluginCmdsWrapper(); - wrapper3.setEntityCountCmds(Collections.singletonList(cmd3)); - wsClient.send(mapper.writeValueAsString(wrapper3)); - - String msg3 = wsClient.waitForReply(); - EntityCountUpdate update3 = mapper.readValue(msg3, EntityCountUpdate.class); + EntityCountUpdate update3 = getWsClient().parseCountReply(getWsClient().waitForReply()); Assert.assertEquals(3, update3.getCmdId()); Assert.assertEquals(1, update3.getCount()); @@ -325,47 +219,26 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { highTemperatureFilter2.setPredicate(predicate2); highTemperatureFilter2.setValueType(EntityKeyValueType.NUMERIC); - DeviceTypeFilter dtf4 = new DeviceTypeFilter(); - dtf4.setDeviceNameFilter("D"); - dtf4.setDeviceType("default"); + DeviceTypeFilter dtf4 = new DeviceTypeFilter("default", "D"); EntityCountQuery edq4 = new EntityCountQuery(dtf4, Collections.singletonList(highTemperatureFilter2)); - EntityCountCmd cmd4 = new EntityCountCmd(4, edq4); - TelemetryPluginCmdsWrapper wrapper4 = new TelemetryPluginCmdsWrapper(); - wrapper4.setEntityCountCmds(Collections.singletonList(cmd4)); - wsClient.send(mapper.writeValueAsString(wrapper4)); + getWsClient().send(cmd4); - String msg4 = wsClient.waitForReply(); - EntityCountUpdate update4 = mapper.readValue(msg4, EntityCountUpdate.class); + EntityCountUpdate update4 = getWsClient().parseCountReply(getWsClient().waitForReply()); Assert.assertEquals(4, update4.getCmdId()); Assert.assertEquals(0, update4.getCount()); } @Test public void testEntityDataLatestWidgetFlow() throws Exception { - Device device = new Device(); - device.setName("Device"); - device.setType("default"); - device.setLabel("testLabel" + (int) (Math.random() * 1000)); - device = doPost("/api/device", device, Device.class); - - long now = System.currentTimeMillis(); - - DeviceTypeFilter dtf = new DeviceTypeFilter(); - dtf.setDeviceNameFilter("D"); - dtf.setDeviceType("default"); - EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), Collections.emptyList(), - Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")), Collections.emptyList()); - - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null); + List keys = List.of(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); + long now = System.currentTimeMillis() - 100; - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), keys, Collections.emptyList()); - wsClient.send(mapper.writeValueAsString(wrapper)); - String msg = wsClient.waitForReply(); - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + EntityDataUpdate update = getWsClient().sendEntityDataQuery(edq); Assert.assertEquals(1, update.getCmdId()); PageData pageData = update.getData(); Assert.assertNotNull(pageData); @@ -379,17 +252,7 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { List tsData = Arrays.asList(dataPoint1); sendTelemetry(device, tsData); - Thread.sleep(100); - - LatestValueCmd latestCmd = new LatestValueCmd(); - latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"))); - cmd = new EntityDataCmd(1, null, null, latestCmd, null); - wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - wsClient.send(mapper.writeValueAsString(wrapper)); - msg = wsClient.waitForReply(); - update = mapper.readValue(msg, EntityDataUpdate.class); + update = getWsClient().subscribeLatestUpdate(keys); Assert.assertEquals(1, update.getCmdId()); @@ -404,11 +267,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { now = System.currentTimeMillis(); TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendTelemetry(device, Arrays.asList(dataPoint2)); - msg = wsClient.waitForUpdate(); - update = mapper.readValue(msg, EntityDataUpdate.class); + update = getWsClient().parseDataReply(getWsClient().waitForUpdate()); Assert.assertEquals(1, update.getCmdId()); List eData = update.getUpdate(); Assert.assertNotNull(eData); @@ -419,43 +281,25 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); //Sending update from the past, while latest value has new timestamp; - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendTelemetry(device, Arrays.asList(dataPoint1)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); //Sending duplicate update again - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendTelemetry(device, Arrays.asList(dataPoint2)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); } @Test public void testEntityDataLatestTsWsCmd() throws Exception { - Device device = new Device(); - device.setName("Device"); - device.setType("default"); - device.setLabel("testLabel" + (int) (Math.random() * 1000)); - device = doPost("/api/device", device, Device.class); - long now = System.currentTimeMillis(); + List keys = List.of(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); - DeviceTypeFilter dtf = new DeviceTypeFilter(); - dtf.setDeviceNameFilter("D"); - dtf.setDeviceType("default"); - EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - LatestValueCmd latestCmd = new LatestValueCmd(); - latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"))); - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + EntityDataUpdate update = getWsClient().subscribeLatestUpdate(keys, dtf); - wsClient.send(mapper.writeValueAsString(wrapper)); - String msg = wsClient.waitForReply(); - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); PageData pageData = update.getData(); Assert.assertNotNull(pageData); @@ -471,13 +315,7 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { Thread.sleep(100); - cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - wsClient.send(mapper.writeValueAsString(wrapper)); - msg = wsClient.waitForReply(); - update = mapper.readValue(msg, EntityDataUpdate.class); + update = getWsClient().subscribeLatestUpdate(keys, dtf); Assert.assertEquals(1, update.getCmdId()); @@ -492,11 +330,9 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { now = System.currentTimeMillis(); TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendTelemetry(device, Arrays.asList(dataPoint2)); - msg = wsClient.waitForUpdate(); - - update = mapper.readValue(msg, EntityDataUpdate.class); + update = getWsClient().parseDataReply(getWsClient().waitForUpdate()); Assert.assertEquals(1, update.getCmdId()); List eData = update.getUpdate(); Assert.assertNotNull(eData); @@ -507,45 +343,24 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); //Sending update from the past, while latest value has new timestamp; - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendTelemetry(device, Arrays.asList(dataPoint1)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); //Sending duplicate update again - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendTelemetry(device, Arrays.asList(dataPoint2)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); } @Test public void testEntityDataLatestAttrWsCmd() throws Exception { - Device device = new Device(); - device.setName("Device"); - device.setType("default"); - device.setLabel("testLabel" + (int) (Math.random() * 1000)); - device = doPost("/api/device", device, Device.class); - long now = System.currentTimeMillis(); + List keys = List.of(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey")); - DeviceTypeFilter dtf = new DeviceTypeFilter(); - dtf.setDeviceNameFilter("D"); - dtf.setDeviceType("default"); - EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - LatestValueCmd latestCmd = new LatestValueCmd(); - latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey"))); - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - wsClient.send(mapper.writeValueAsString(wrapper)); - String msg = wsClient.waitForReply(); - Assert.assertNotNull(msg); - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + EntityDataUpdate update = getWsClient().subscribeLatestUpdate(keys, dtf); Assert.assertEquals(1, update.getCmdId()); PageData pageData = update.getData(); Assert.assertNotNull(pageData); @@ -555,15 +370,14 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs()); Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue()); - - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); Thread.sleep(500); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); List tsData = Arrays.asList(dataPoint1); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); - msg = wsClient.waitForUpdate(); + String msg = getWsClient().waitForUpdate(); Assert.assertNotNull(msg); update = mapper.readValue(msg, EntityDataUpdate.class); @@ -579,10 +393,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { now = System.currentTimeMillis(); AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L)); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); Thread.sleep(500); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); - msg = wsClient.waitForUpdate(); + msg = getWsClient().waitForUpdate(); Assert.assertNotNull(msg); update = mapper.readValue(msg, EntityDataUpdate.class); @@ -596,51 +410,31 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), tsValue); //Sending update from the past, while latest value has new timestamp; - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); Thread.sleep(500); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); //Sending duplicate update again - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); Thread.sleep(500); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); } @Test public void testEntityDataLatestAttrTypesWsCmd() throws Exception { - Device device = new Device(); - device.setName("Device"); - device.setType("default"); - device.setLabel("testLabel" + (int) (Math.random() * 1000)); - device = doPost("/api/device", device, Device.class); - long now = System.currentTimeMillis(); + List keys = List.of( + new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey"), + new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, "clientAttributeKey"), + new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, "sharedAttributeKey"), + new EntityKey(EntityKeyType.ATTRIBUTE, "anyAttributeKey")); + + EntityDataUpdate update = getWsClient().subscribeLatestUpdate(keys, dtf); - DeviceTypeFilter dtf = new DeviceTypeFilter(); - dtf.setDeviceNameFilter("D"); - dtf.setDeviceType("default"); - EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); - - LatestValueCmd latestCmd = new LatestValueCmd(); - List keys = new ArrayList<>(); - keys.add(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey")); - keys.add(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, "clientAttributeKey")); - keys.add(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, "sharedAttributeKey")); - keys.add(new EntityKey(EntityKeyType.ATTRIBUTE, "anyAttributeKey")); - latestCmd.setKeys(keys); - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); - - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - - wsClient.send(mapper.writeValueAsString(wrapper)); - String msg = wsClient.waitForReply(); - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); PageData pageData = update.getData(); Assert.assertNotNull(pageData); @@ -659,14 +453,14 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs()); Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue()); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); List tsData = Arrays.asList(dataPoint1); Thread.sleep(100); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); - msg = wsClient.waitForUpdate(); + String msg = getWsClient().waitForUpdate(); Assert.assertNotNull(msg); update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); @@ -679,22 +473,22 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), attrValue); //Sending update from the past, while latest value has new timestamp; - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint1)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); //Sending duplicate update again - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint1)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); Assert.assertNull(msg); //Sending update from the past, while latest value has new timestamp; - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("sharedAttributeKey", 42L)); sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint2)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); eData = update.getUpdate(); @@ -705,10 +499,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { attrValue = eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey"); Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), attrValue); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); AttributeKvEntry dataPoint3 = new BaseAttributeKvEntry(now, new LongDataEntry("clientAttributeKey", 42L)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint3)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); eData = update.getUpdate(); @@ -719,10 +513,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { attrValue = eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey"); Assert.assertEquals(new TsValue(dataPoint3.getLastUpdateTs(), dataPoint3.getValueAsString()), attrValue); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); AttributeKvEntry dataPoint4 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 42L)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint4)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); eData = update.getUpdate(); @@ -733,10 +527,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"); Assert.assertEquals(new TsValue(dataPoint4.getLastUpdateTs(), dataPoint4.getValueAsString()), attrValue); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); AttributeKvEntry dataPoint5 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 43L)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint5)); - msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); + msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1)); update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); eData = update.getUpdate(); diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index ddc0c09157..9dcba19044 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -30,9 +30,4 @@ import org.thingsboard.server.queue.memory.InMemoryStorage; }) public class ControllerSqlTestSuite { - @BeforeClass - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java index 3abd311aa3..6af6176b06 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java +++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java @@ -18,9 +18,25 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityFilter; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd; +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; import java.net.URI; import java.nio.channels.NotYetConnectedException; +import java.util.Collections; +import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -73,6 +89,18 @@ public class TbTestWebSocketClient extends WebSocketClient { super.send(text); } + public void send(EntityDataCmd cmd) throws NotYetConnectedException { + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + this.send(JacksonUtil.toString(wrapper)); + } + + public void send(EntityCountCmd cmd) throws NotYetConnectedException { + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityCountCmds(Collections.singletonList(cmd)); + this.send(JacksonUtil.toString(wrapper)); + } + public String waitForUpdate() { return waitForUpdate(TimeUnit.SECONDS.toMillis(3)); } @@ -94,4 +122,95 @@ public class TbTestWebSocketClient extends WebSocketClient { } return lastMsg; } + + public EntityDataUpdate parseDataReply(String msg) { + return JacksonUtil.fromString(msg, EntityDataUpdate.class); + } + + public EntityCountUpdate parseCountReply(String msg) { + return JacksonUtil.fromString(msg, EntityCountUpdate.class); + } + + public EntityDataUpdate subscribeLatestUpdate(List keys, EntityFilter entityFilter) { + EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + return subscribeLatestUpdate(keys, edq); + } + + public EntityDataUpdate subscribeLatestUpdate(List keys) { + return subscribeLatestUpdate(keys, (EntityDataQuery) null); + } + + public EntityDataUpdate subscribeLatestUpdate(List keys, EntityDataQuery edq) { + LatestValueCmd latestCmd = new LatestValueCmd(); + latestCmd.setKeys(keys); + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); + send(cmd); + return parseDataReply(waitForReply()); + } + + public EntityDataUpdate subscribeTsUpdate(List keys, long startTs, long timeWindow) { + return subscribeTsUpdate(keys, startTs, timeWindow, (EntityDataQuery) null); + } + + public EntityDataUpdate subscribeTsUpdate(List keys, long startTs, long timeWindow, EntityFilter entityFilter) { + EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + return subscribeTsUpdate(keys, startTs, timeWindow, edq); + } + + public EntityDataUpdate subscribeTsUpdate(List keys, long startTs, long timeWindow, EntityDataQuery edq) { + TimeSeriesCmd tsCmd = new TimeSeriesCmd(); + tsCmd.setKeys(keys); + tsCmd.setAgg(Aggregation.NONE); + tsCmd.setLimit(1000); + tsCmd.setStartTs(startTs - timeWindow); + tsCmd.setTimeWindow(timeWindow); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, tsCmd); + + send(cmd); + return parseDataReply(waitForReply()); + } + + public EntityDataUpdate sendHistoryCmd(List keys, long startTs, long timeWindow) { + return sendHistoryCmd(keys, startTs, timeWindow, (EntityDataQuery) null); + } + + public EntityDataUpdate sendHistoryCmd(List keys, long startTs, long timeWindow, EntityFilter entityFilter) { + EntityDataQuery edq = new EntityDataQuery(entityFilter, + new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + return sendHistoryCmd(keys, startTs, timeWindow, edq); + } + + public EntityDataUpdate sendHistoryCmd(List keys, long startTs, long timeWindow, EntityDataQuery edq) { + EntityHistoryCmd historyCmd = new EntityHistoryCmd(); + historyCmd.setKeys(keys); + historyCmd.setAgg(Aggregation.NONE); + historyCmd.setLimit(1000); + historyCmd.setStartTs(startTs - timeWindow); + historyCmd.setEndTs(startTs); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, historyCmd, null, null); + + send(cmd); + return parseDataReply(this.waitForReply()); + } + + public EntityDataUpdate sendEntityDataQuery(EntityDataQuery edq) { + log.warn("sendEntityDataQuery {}", edq); + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null); + send(cmd); + String msg = this.waitForReply(); + return parseDataReply(msg); + } + + public EntityDataUpdate sendEntityDataQuery(EntityFilter entityFilter) { + log.warn("sendEntityDataQuery {}", entityFilter); + EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + return sendEntityDataQuery(edq); + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java similarity index 76% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceTest.java rename to application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java index 85d7f7714d..bba390448c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.mqtt.claim.sql; +package org.thingsboard.server.controller.sql; +import org.thingsboard.server.controller.BaseAlarmControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimDeviceTest; @DaoSqlTest -public class MqttClaimDeviceTest extends AbstractMqttClaimDeviceTest { +public class AlarmControllerSqlTest extends BaseAlarmControllerTest { } 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 9392448c6c..e815378d27 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -32,6 +32,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; @@ -42,6 +43,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -80,6 +82,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -110,6 +113,7 @@ import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; +import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; @@ -128,6 +132,9 @@ import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@TestPropertySource(properties = { + "edges.enabled=true", +}) abstract public class BaseEdgeTest extends AbstractControllerTest { private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat"; @@ -192,7 +199,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void installation() throws Exception { edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); - DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, null); + DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME); extendDeviceProfileData(deviceProfile); doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); @@ -331,6 +338,34 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertNotNull(jsonNode.get("test")); } + @Test + public void testDeviceProfiles() throws Exception { + // 1 + DeviceProfile deviceProfile = this.createDeviceProfile("ONE_MORE_DEVICE_PROFILE", null); + extendDeviceProfileData(deviceProfile); + edgeImitator.expectMessageAmount(1); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); + + // 2 + edgeImitator.expectMessageAmount(1); + doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); + } + @Test public void testDevices() throws Exception { // 1 @@ -385,6 +420,40 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { } + @Test + public void testDeviceReachedMaximumAllowedOnCloud() throws Exception { + // update tenant profile configuration + loginSysAdmin(); + TenantProfile tenantProfile = doGet("/api/tenantProfile/" + savedTenant.getTenantProfileId().getId(), TenantProfile.class); + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); + profileConfiguration.setMaxDevices(1); + tenantProfile.getProfileData().setConfiguration(profileConfiguration); + doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class); + + loginTenantAdmin(); + + UUID uuid = Uuids.timeBased(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); + deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + deviceUpdateMsgBuilder.setName("Edge Device"); + deviceUpdateMsgBuilder.setType("default"); + deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); + + edgeImitator.expectResponsesAmount(1); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + + UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg(); + Assert.assertTrue(latestResponseMsg.getSuccess()); + } + @Test public void testAssets() throws Exception { // 1 @@ -547,8 +616,6 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { ruleChainMetaData.addConnectionInfo(0, 2, "fail"); ruleChainMetaData.addConnectionInfo(1, 2, "success"); - ruleChainMetaData.addRuleChainConnectionInfo(2, edge.getRootRuleChainId(), "success", mapper.createObjectNode()); - doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); } @@ -909,7 +976,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.save(edgeEvent); + edgeEventService.saveAsync(edgeEvent).get(); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -940,12 +1007,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { testAttributesDeleteMsg(device); } - private void testAttributesUpdatedMsg(Device device) throws JsonProcessingException, InterruptedException { + private void testAttributesUpdatedMsg(Device device) throws Exception { String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}"; JsonNode attributesEntityData = mapper.readTree(attributesData); EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData); edgeImitator.expectMessageAmount(1); - edgeEventService.save(edgeEvent1); + edgeEventService.saveAsync(edgeEvent1).get(); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -965,12 +1032,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals("value1", keyValueProto.getStringV()); } - private void testPostAttributesMsg(Device device) throws JsonProcessingException, InterruptedException { + private void testPostAttributesMsg(Device device) throws Exception { String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}"; JsonNode postAttributesEntityData = mapper.readTree(postAttributesData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData); edgeImitator.expectMessageAmount(1); - edgeEventService.save(edgeEvent); + edgeEventService.saveAsync(edgeEvent).get(); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -990,12 +1057,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals("value2", keyValueProto.getStringV()); } - private void testAttributesDeleteMsg(Device device) throws JsonProcessingException, InterruptedException { + private void testAttributesDeleteMsg(Device device) throws Exception { String deleteAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"keys\":[\"key1\",\"key2\"]}"; JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData); EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData); edgeImitator.expectMessageAmount(1); - edgeEventService.save(edgeEvent); + edgeEventService.saveAsync(edgeEvent).get(); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -1030,7 +1097,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.save(edgeEvent); + edgeEventService.saveAsync(edgeEvent).get(); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); Assert.assertTrue(edgeImitator.waitForMessages()); @@ -1055,7 +1122,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.save(edgeEvent); + edgeEventService.saveAsync(edgeEvent).get(); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); } diff --git a/application/src/test/java/org/thingsboard/server/edge/EdgeSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/edge/EdgeSqlTestSuite.java index 70894cdbbe..e7fff461f3 100644 --- a/application/src/test/java/org/thingsboard/server/edge/EdgeSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/edge/EdgeSqlTestSuite.java @@ -26,8 +26,4 @@ import org.thingsboard.server.queue.memory.InMemoryStorage; }) public class EdgeSqlTestSuite { - @BeforeClass - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } } diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index 89c61f5a14..cfb652dd9f 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -88,6 +88,9 @@ public class EdgeImitator { @Getter private List downlinkMsgs; + @Getter + private UplinkResponseMsg latestResponseMsg; + private boolean connected = false; public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException { @@ -132,6 +135,7 @@ public class EdgeImitator { private void onUplinkResponse(UplinkResponseMsg msg) { log.info("onUplinkResponse: {}", msg); + latestResponseMsg = msg; responsesLatch.countDown(); } diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java index 36e08f5859..cb99c21165 100644 --- a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java @@ -27,9 +27,4 @@ import org.thingsboard.server.queue.memory.InMemoryStorage; }) public class RuleEngineSqlTestSuite { - @BeforeClass - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } - } diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index 793063d2d7..9d96fe9d9c 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -15,19 +15,28 @@ */ package org.thingsboard.server.rules.flow; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; +import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.common.data.*; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; @@ -38,12 +47,14 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.event.EventService; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +import static org.mockito.Mockito.spy; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -61,8 +72,26 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule @Autowired protected AttributesService attributesService; + @Autowired + protected EventService eventService; + @Before public void beforeTest() throws Exception { + + EventService spyEventService = spy(eventService); + + Mockito.doAnswer((Answer>) invocation -> { + Object[] args = invocation.getArguments(); + Event event = (Event) args[0]; + ListenableFuture future = eventService.saveAsync(event); + try { + future.get(); + } catch (Exception e) {} + return future; + }).when(spyEventService).saveAsync(Mockito.any(Event.class)); + + ReflectionTestUtils.setField(actorSystem, "eventService", spyEventService); + loginSysAdmin(); Tenant tenant = new Tenant(); @@ -213,16 +242,27 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); ruleNode1.setConfiguration(mapper.valueToTree(configuration1)); - rootMetaData.setNodes(Collections.singletonList(ruleNode1)); + RuleNode ruleNode12 = new RuleNode(); + ruleNode12.setName("Simple Rule Node 1"); + ruleNode12.setType(org.thingsboard.rule.engine.flow.TbRuleChainInputNode.class.getName()); + ruleNode12.setDebugMode(true); + TbRuleChainInputNodeConfiguration configuration12 = new TbRuleChainInputNodeConfiguration(); + configuration12.setRuleChainId(secondaryRuleChain.getId().getId().toString()); + ruleNode12.setConfiguration(mapper.valueToTree(configuration12)); + + rootMetaData.setNodes(Arrays.asList(ruleNode1, ruleNode12)); rootMetaData.setFirstNodeIndex(0); - rootMetaData.addRuleChainConnectionInfo(0, secondaryRuleChain.getId(), "Success", mapper.createObjectNode()); + NodeConnectionInfo connection = new NodeConnectionInfo(); + connection.setFromIndex(0); + connection.setToIndex(1); + connection.setType("Success"); + rootMetaData.setConnections(Collections.singletonList(connection)); rootMetaData = saveRuleChainMetaData(rootMetaData); Assert.assertNotNull(rootMetaData); rootRuleChain = getRuleChain(rootRuleChain.getId()); Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId()); - RuleChainMetaData secondaryMetaData = new RuleChainMetaData(); secondaryMetaData.setRuleChainId(secondaryRuleChain.getId()); diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 8bae26583f..9f82b6ca2b 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -15,13 +15,16 @@ */ package org.thingsboard.server.rules.lifecycle; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; +import org.mockito.stubbing.Answer; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.DataConstants; @@ -42,6 +45,7 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.queue.memory.InMemoryStorage; import java.util.Collections; @@ -50,6 +54,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.spy; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; /** @@ -67,8 +72,29 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac @Autowired protected AttributesService attributesService; + @Autowired + protected EventService eventService; + + @Autowired + protected InMemoryStorage storage; + @Before public void beforeTest() throws Exception { + + EventService spyEventService = spy(eventService); + + Mockito.doAnswer((Answer>) invocation -> { + Object[] args = invocation.getArguments(); + Event event = (Event) args[0]; + ListenableFuture future = eventService.saveAsync(event); + try { + future.get(); + } catch (Exception e) {} + return future; + }).when(spyEventService).saveAsync(Mockito.any(Event.class)); + + ReflectionTestUtils.setField(actorSystem, "eventService", spyEventService); + loginSysAdmin(); Tenant tenant = new Tenant(); @@ -136,7 +162,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis()))); await("total inMemory queue lag is empty").atMost(30, TimeUnit.SECONDS) - .until(() -> InMemoryStorage.getInstance().getLagTotal() == 0); + .until(() -> storage.getLagTotal() == 0); Thread.sleep(1000); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); diff --git a/application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java index bc3348c9f1..11f06875fe 100644 --- a/application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java @@ -27,9 +27,4 @@ import org.thingsboard.server.queue.memory.InMemoryStorage; }) public class ServiceSqlTestSuite { - @BeforeClass - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } - } diff --git a/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java new file mode 100644 index 0000000000..fde595ea3b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java @@ -0,0 +1,86 @@ +/** + * Copyright © 2016-2022 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.apiusage; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.rule.engine.api.MailService; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; + +import java.util.UUID; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; + +@RunWith(MockitoJUnitRunner.class) +public class DefaultTbApiUsageStateServiceTest { + + @Mock + TenantService tenantService; + @Mock + TimeseriesService tsService; + @Mock + TbClusterService clusterService; + @Mock + PartitionService partitionService; + @Mock + TenantApiUsageState tenantUsageStateMock; + @Mock + ApiUsageStateService apiUsageStateService; + @Mock + TbTenantProfileCache tenantProfileCache; + @Mock + MailService mailService; + @Mock + DbCallbackExecutorService dbExecutor; + + TenantId tenantId = TenantId.fromUUID(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112")); + + DefaultTbApiUsageStateService service; + + @Before + public void setUp() { + service = spy(new DefaultTbApiUsageStateService(clusterService, partitionService, tenantService, tsService, apiUsageStateService, tenantProfileCache, mailService, dbExecutor)); + } + + @Test + public void givenTenantIdFromEntityStatesMap_whenGetApiUsageState() { + service.myUsageStates.put(tenantId, tenantUsageStateMock); + ApiUsageState tenantUsageState = service.getApiUsageState(tenantId); + assertThat(tenantUsageState, is(tenantUsageStateMock.getApiUsageState())); + Mockito.verify(service, never()).getOrFetchState(tenantId, tenantId); + } + +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java b/application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java new file mode 100644 index 0000000000..17803bac96 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2022 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.script; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.UUID; + +@Slf4j +@Service +@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "mock") +public class MockJsInvokeService implements JsInvokeService { + + @Override + public ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) { + log.warn("eval {} {} {} {}", tenantId, scriptType, scriptBody, argNames); + return Futures.immediateFuture(UUID.randomUUID()); + } + + @Override + public ListenableFuture invokeFunction(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args) { + log.warn("invokeFunction {} {} {} {}", tenantId, customerId, scriptId, args); + return Futures.immediateFuture("{}"); + } + + @Override + public ListenableFuture release(UUID scriptId) { + log.warn("release {}", scriptId); + return Futures.immediateFuture(null); + } +} diff --git a/application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java b/application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java new file mode 100644 index 0000000000..7cbc8c03d2 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/sms/smpp/SmppSmsSenderTest.java @@ -0,0 +1,121 @@ +/** + * Copyright © 2016-2022 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.sms.smpp; + +import org.apache.commons.lang3.StringUtils; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.smpp.Session; +import org.smpp.pdu.SubmitSMResp; +import org.thingsboard.server.common.data.sms.config.SmppSmsProviderConfiguration; + +import java.lang.reflect.Constructor; +import java.net.UnknownHostException; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class SmppSmsSenderTest { + + SmppSmsSender smppSmsSender; + SmppSmsProviderConfiguration smppConfig; + Session smppSession; + + @Before + public void beforeEach() throws Exception { + Constructor constructor = SmppSmsSender.class.getDeclaredConstructor(); + constructor.setAccessible(true); + smppSmsSender = spy(constructor.newInstance()); + + smppSession = mock(Session.class); + smppSmsSender.smppSession = smppSession; + + smppConfig = new SmppSmsProviderConfiguration(); + smppSmsSender.config = smppConfig; + } + + @Test + public void testSendSms() throws Exception { + when(smppSession.isOpened()).thenReturn(true); + when(smppSession.submit(any())).thenReturn(new SubmitSMResp()); + setDefaultSmppConfig(); + + String number = "123545"; + String message = "message"; + smppSmsSender.sendSms(number, message); + + verify(smppSmsSender, never()).initSmppSession(); + verify(smppSession).submit(argThat(submitRequest -> { + try { + return submitRequest.getShortMessage().equals(message) && + submitRequest.getDestAddr().getAddress().equals(number) && + submitRequest.getServiceType().equals(smppConfig.getServiceType()) && + (StringUtils.isEmpty(smppConfig.getSourceAddress()) ? submitRequest.getSourceAddr().getAddress().equals("") + : submitRequest.getSourceAddr().getAddress().equals(smppConfig.getSourceAddress()) && + submitRequest.getSourceAddr().getTon() == smppConfig.getSourceTon() && + submitRequest.getSourceAddr().getNpi() == smppConfig.getSourceNpi()) && + submitRequest.getDestAddr().getTon() == smppConfig.getDestinationTon() && + submitRequest.getDestAddr().getNpi() == smppConfig.getDestinationNpi() && + submitRequest.getDataCoding() == smppConfig.getCodingScheme() && + submitRequest.getReplaceIfPresentFlag() == 0 && + submitRequest.getEsmClass() == 0 && + submitRequest.getProtocolId() == 0 && + submitRequest.getPriorityFlag() == 0 && + submitRequest.getRegisteredDelivery() == 0 && + submitRequest.getSmDefaultMsgId() == 0; + } catch (Exception e) { + fail(e.getMessage()); + return false; + } + })); + } + + private void setDefaultSmppConfig() { + smppConfig.setProtocolVersion("3.3"); + smppConfig.setHost("smpphost"); + smppConfig.setPort(5687); + smppConfig.setSystemId("213131"); + smppConfig.setPassword("35125q"); + + smppConfig.setSystemType(""); + smppConfig.setBindType(SmppSmsProviderConfiguration.SmppBindType.TX); + smppConfig.setServiceType(""); + + smppConfig.setSourceAddress(""); + smppConfig.setSourceTon((byte) 5); + smppConfig.setSourceNpi((byte) 0); + + smppConfig.setDestinationTon((byte) 5); + smppConfig.setDestinationNpi((byte) 0); + + smppConfig.setAddressRange(""); + smppConfig.setCodingScheme((byte) 0); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java index 2598bbe7d1..c1b87d9ac5 100644 --- a/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java @@ -56,8 +56,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @DaoSqlTest public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest { - static final int TIMEOUT = 30; - final String TOTALIZER = "Totalizer"; final int TTL = 99999; final String GENERIC_CUMULATIVE_OBJ = "genericCumulativeObj"; diff --git a/application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java b/application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java index acfe122d17..0d115ded7d 100644 --- a/application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java @@ -42,7 +42,6 @@ public class EventsCleanUpServiceTest { public void givenInterval_whenRandomDelay_ThenDelayInInterval() { log.info("randomDelay {}", randomDelayMs); log.info("executionIntervalMs {}", executionIntervalMs); - assertThat(executionIntervalMs, is(2220000L)); assertThat(randomDelayMs, greaterThanOrEqualTo(0L)); assertThat(randomDelayMs, lessThanOrEqualTo(executionIntervalMs)); } diff --git a/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java index 23368d1416..06f57b55f6 100644 --- a/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java +++ b/application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.system; import org.junit.Before; import org.junit.Test; +import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.thingsboard.server.common.data.Device; @@ -35,6 +36,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * @author Andrew Shvayka */ +@TestPropertySource(properties = { + "transport.http.enabled=true", +}) public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest { private static final AtomicInteger idSeq = new AtomicInteger(new Random(System.currentTimeMillis()).nextInt()); diff --git a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java index 695e90fd33..714ad67519 100644 --- a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java @@ -29,9 +29,4 @@ import org.thingsboard.server.queue.memory.InMemoryStorage; }) public class SystemSqlTestSuite { - @BeforeClass - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java index 6a1565402d..09acd96d47 100644 --- a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java @@ -18,8 +18,6 @@ package org.thingsboard.server.transport; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.gen.transport.TransportProtos; @@ -27,8 +25,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @Slf4j public abstract class AbstractTransportIntegrationTest extends AbstractControllerTest { @@ -95,21 +91,11 @@ public abstract class AbstractTransportIntegrationTest extends AbstractControlle " optional string params = 3;\n" + "}"; - protected Tenant savedTenant; - protected User tenantAdmin; - protected Device savedDevice; protected String accessToken; protected DeviceProfile deviceProfile; - protected void processAfterTest() throws Exception { - loginSysAdmin(); - if (savedTenant != null) { - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); - } - } - protected List getKvProtos(List expectedKeys) { List keyValueProtos = new ArrayList<>(); TransportProtos.KeyValueProto strKeyValueProto = getKeyValueProto(expectedKeys.get(0), "value1", TransportProtos.KeyValueType.STRING_V); diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java index dc74fc7655..255057cc2f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java @@ -40,9 +40,4 @@ public class TransportNoSqlTestSuite { ), "cassandra-test.yaml", 30000l); - @BeforeClass - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index 26fc5acdaa..3f25ded621 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -15,28 +15,21 @@ */ package org.thingsboard.server.transport; -import org.junit.BeforeClass; import org.junit.extensions.cpsuite.ClasspathSuite; import org.junit.runner.RunWith; -import org.thingsboard.server.queue.memory.InMemoryStorage; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.transport.*.rpc.sql.*Test", + "org.thingsboard.server.transport.*.rpc.*Test", "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", - "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", - "org.thingsboard.server.transport.*.attributes.request.sql.*Test", - "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test", - "org.thingsboard.server.transport.*.credentials.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.attributes.*Test", + "org.thingsboard.server.transport.*.attributes.updates.*Test", + "org.thingsboard.server.transport.*.attributes.request.*Test", + "org.thingsboard.server.transport.*.claim.*Test", + "org.thingsboard.server.transport.*.provision.*Test", + "org.thingsboard.server.transport.*.credentials.*Test", "org.thingsboard.server.transport.lwm2m.*.sql.*Test" }) public class TransportSqlTestSuite { - @BeforeClass - public static void cleanupInMemStorage() { - InMemoryStorage.getInstance().cleanup(); - } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java index cc648d52d5..b3ab39fd51 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java @@ -17,17 +17,16 @@ package org.thingsboard.server.transport.coap; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapClient; -import org.junit.Assert; -import org.springframework.util.StringUtils; +import org.junit.After; +import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; @@ -41,7 +40,6 @@ import org.thingsboard.server.common.data.device.profile.EfentoCoapDeviceTypeCon import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.transport.AbstractTransportIntegrationTest; @@ -49,6 +47,9 @@ import org.thingsboard.server.transport.AbstractTransportIntegrationTest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +@TestPropertySource(properties = { + "transport.coap.enabled=true", +}) @Slf4j public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest { @@ -56,133 +57,104 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg protected CoapClient client; - @Override protected void processAfterTest() throws Exception { if (client != null) { client.shutdown(); } - super.processAfterTest(); } - protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { - this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); - } - - protected void processBeforeTest(String deviceName, - CoapDeviceType coapDeviceType, - TransportPayloadType payloadType, - String telemetryProtoSchema, - String attributesProtoSchema, - String rpcResponseProtoSchema, - String rpcRequestProtoSchema, - String provisionKey, - String provisionSecret, - DeviceProfileProvisionType provisionType - ) throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - - Device device = new Device(); - device.setName(deviceName); - device.setType("default"); - - if (coapDeviceType != null) { - DeviceProfile coapDeviceProfile = createCoapDeviceProfile(payloadType, coapDeviceType, provisionSecret, provisionType, provisionKey, attributesProtoSchema, telemetryProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema); - deviceProfile = doPost("/api/deviceProfile", coapDeviceProfile, DeviceProfile.class); - device.setType(deviceProfile.getName()); - device.setDeviceProfileId(deviceProfile.getId()); - } - - savedDevice = doPost("/api/device", device, Device.class); - + protected void processBeforeTest(CoapTestConfigProperties config) throws Exception { + loginTenantAdmin(); + deviceProfile = createCoapDeviceProfile(config); + assertNotNull(deviceProfile); + savedDevice = createDevice(config.getDeviceName(), deviceProfile.getName()); DeviceCredentials deviceCredentials = doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); - + assertNotNull(deviceCredentials); assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); accessToken = deviceCredentials.getCredentialsId(); assertNotNull(accessToken); - } - protected DeviceProfile createCoapDeviceProfile(TransportPayloadType transportPayloadType, CoapDeviceType coapDeviceType, - String provisionSecret, DeviceProfileProvisionType provisionType, - String provisionKey, String attributesProtoSchema, - String telemetryProtoSchema, String rpcResponseProtoSchema, String rpcRequestProtoSchema) { - DeviceProfile deviceProfile = new DeviceProfile(); - deviceProfile.setName(transportPayloadType.name()); - deviceProfile.setType(DeviceProfileType.DEFAULT); - deviceProfile.setProvisionType(provisionType); - deviceProfile.setProvisionDeviceKey(provisionKey); - deviceProfile.setDescription(transportPayloadType.name() + " Test"); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - deviceProfile.setTransportType(DeviceTransportType.COAP); - CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = new CoapDeviceProfileTransportConfiguration(); - CoapDeviceTypeConfiguration coapDeviceTypeConfiguration; - if (CoapDeviceType.DEFAULT.equals(coapDeviceType)) { - DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = new DefaultCoapDeviceTypeConfiguration(); - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; - if (TransportPayloadType.PROTOBUF.equals(transportPayloadType)) { - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); - if (StringUtils.isEmpty(telemetryProtoSchema)) { - telemetryProtoSchema = DEVICE_TELEMETRY_PROTO_SCHEMA; - } - if (StringUtils.isEmpty(attributesProtoSchema)) { - attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; - } - if (StringUtils.isEmpty(rpcResponseProtoSchema)) { - rpcResponseProtoSchema = DEVICE_RPC_RESPONSE_PROTO_SCHEMA; - } - if (StringUtils.isEmpty(rpcRequestProtoSchema)) { - rpcRequestProtoSchema = DEVICE_RPC_REQUEST_PROTO_SCHEMA; + protected DeviceProfile createCoapDeviceProfile(CoapTestConfigProperties config) throws Exception { + CoapDeviceType coapDeviceType = config.getCoapDeviceType(); + if (coapDeviceType == null) { + DeviceProfileInfo defaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class); + return doGet("/api/deviceProfile/" + defaultDeviceProfileInfo.getId().getId(), DeviceProfile.class); + } else { + TransportPayloadType transportPayloadType = config.getTransportPayloadType(); + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setName(transportPayloadType.name()); + deviceProfile.setType(DeviceProfileType.DEFAULT); + DeviceProfileProvisionType provisionType = config.getProvisionType() != null ? + config.getProvisionType() : DeviceProfileProvisionType.DISABLED; + deviceProfile.setProvisionType(provisionType); + deviceProfile.setProvisionDeviceKey(config.getProvisionKey()); + deviceProfile.setDescription(transportPayloadType.name() + " Test"); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + deviceProfile.setTransportType(DeviceTransportType.COAP); + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = new CoapDeviceProfileTransportConfiguration(); + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration; + if (CoapDeviceType.DEFAULT.equals(coapDeviceType)) { + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = new DefaultCoapDeviceTypeConfiguration(); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; + if (TransportPayloadType.PROTOBUF.equals(transportPayloadType)) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + String telemetryProtoSchema = config.getTelemetryProtoSchema(); + String attributesProtoSchema = config.getAttributesProtoSchema(); + String rpcResponseProtoSchema = config.getRpcResponseProtoSchema(); + String rpcRequestProtoSchema = config.getRpcRequestProtoSchema(); + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema( + telemetryProtoSchema != null ? telemetryProtoSchema : DEVICE_TELEMETRY_PROTO_SCHEMA + ); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema( + attributesProtoSchema != null ? attributesProtoSchema : DEVICE_ATTRIBUTES_PROTO_SCHEMA + ); + protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema( + rpcResponseProtoSchema != null ? rpcResponseProtoSchema : DEVICE_RPC_RESPONSE_PROTO_SCHEMA + ); + protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema( + rpcRequestProtoSchema != null ? rpcRequestProtoSchema : DEVICE_RPC_REQUEST_PROTO_SCHEMA + ); + transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; + } else { + transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); } - protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); - protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); - protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema); - protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema); - transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; + defaultCoapDeviceTypeConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + coapDeviceTypeConfiguration = defaultCoapDeviceTypeConfiguration; } else { - transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); + coapDeviceTypeConfiguration = new EfentoCoapDeviceTypeConfiguration(); } - defaultCoapDeviceTypeConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); - coapDeviceTypeConfiguration = defaultCoapDeviceTypeConfiguration; - } else { - coapDeviceTypeConfiguration = new EfentoCoapDeviceTypeConfiguration(); - } - coapDeviceProfileTransportConfiguration.setCoapDeviceTypeConfiguration(coapDeviceTypeConfiguration); - deviceProfileData.setTransportConfiguration(coapDeviceProfileTransportConfiguration); - DeviceProfileProvisionConfiguration provisionConfiguration; - switch (provisionType) { - case ALLOW_CREATE_NEW_DEVICES: - provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(provisionSecret); - break; - case CHECK_PRE_PROVISIONED_DEVICES: - provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(provisionSecret); - break; - case DISABLED: - default: - provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); - break; + coapDeviceProfileTransportConfiguration.setCoapDeviceTypeConfiguration(coapDeviceTypeConfiguration); + deviceProfileData.setTransportConfiguration(coapDeviceProfileTransportConfiguration); + DeviceProfileProvisionConfiguration provisionConfiguration; + switch (provisionType) { + case ALLOW_CREATE_NEW_DEVICES: + provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); + break; + case CHECK_PRE_PROVISIONED_DEVICES: + provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); + break; + case DISABLED: + default: + provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(config.getProvisionSecret()); + break; + } + deviceProfileData.setProvisionConfiguration(provisionConfiguration); + deviceProfileData.setConfiguration(configuration); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setDefault(false); + deviceProfile.setDefaultRuleChainId(null); + return doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); } - deviceProfileData.setProvisionConfiguration(provisionConfiguration); - deviceProfileData.setConfiguration(configuration); - deviceProfile.setProfileData(deviceProfileData); - deviceProfile.setDefault(false); - deviceProfile.setDefaultRuleChainId(null); - return deviceProfile; + } + + protected Device createDevice(String name, String type) throws Exception { + Device device = new Device(); + device.setName(name); + device.setType(type); + return doPost("/api/device", device, Device.class); } protected CoapClient getCoapClient(FeatureType featureType) { diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestConfigProperties.java b/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestConfigProperties.java new file mode 100644 index 0000000000..c387a3b41a --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestConfigProperties.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2022 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.coap; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Data +@Builder +public class CoapTestConfigProperties { + + String deviceName; + + CoapDeviceType coapDeviceType; + + TransportPayloadType transportPayloadType; + + String telemetryTopicFilter; + String attributesTopicFilter; + + String telemetryProtoSchema; + String attributesProtoSchema; + String rpcResponseProtoSchema; + String rpcRequestProtoSchema; + + DeviceProfileProvisionType provisionType; + String provisionKey; + String provisionSecret; + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java index 2f77ce9756..9d0078aa3f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java @@ -16,10 +16,8 @@ package org.thingsboard.server.transport.coap.attributes; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; -import org.thingsboard.server.common.data.CoapDeviceType; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import java.util.ArrayList; import java.util.List; @@ -30,14 +28,6 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," + "\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; - protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { - super.processBeforeTest(deviceName, coapDeviceType, payloadType); - } - - protected void processAfterTest() throws Exception { - super.processAfterTest(); - } - protected List getTsKvProtoList() { TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V); TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestIntegrationTest.java similarity index 90% rename from application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestIntegrationTest.java index 3183d32e58..977502e0e6 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestIntegrationTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap.attributes.request; import com.fasterxml.jackson.core.type.TypeReference; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; @@ -26,6 +25,8 @@ import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; import org.thingsboard.server.common.msg.session.FeatureType; @@ -37,13 +38,17 @@ import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractCoapAttributesRequestIntegrationTest extends AbstractCoapAttributesIntegrationTest { +@DaoSqlTest +public class CoapAttributesRequestIntegrationTest extends AbstractCoapAttributesIntegrationTest { protected static final long CLIENT_REQUEST_TIMEOUT = 60000L; @Before public void beforeTest() throws Exception { - processBeforeTest("Test Request attribute values from the server", null, null); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server") + .build(); + processBeforeTest(configProperties); } @After diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestJsonIntegrationTest.java similarity index 67% rename from application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestJsonIntegrationTest.java index b8aa99c9a3..e421fbf1a0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestJsonIntegrationTest.java @@ -21,13 +21,21 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j -public abstract class AbstractCoapAttributesRequestJsonIntegrationTest extends AbstractCoapAttributesRequestIntegrationTest { +@DaoSqlTest +public class CoapAttributesRequestJsonIntegrationTest extends CoapAttributesRequestIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Request attribute values from the server json", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server json") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @After diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestProtoIntegrationTest.java similarity index 93% rename from application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestProtoIntegrationTest.java index 0cbd34ebb7..7bdcce1400 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestProtoIntegrationTest.java @@ -36,7 +36,9 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.util.List; import java.util.stream.Collectors; @@ -47,7 +49,8 @@ import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractCoapAttributesRequestProtoIntegrationTest extends AbstractCoapAttributesRequestIntegrationTest { +@DaoSqlTest +public class CoapAttributesRequestProtoIntegrationTest extends CoapAttributesRequestIntegrationTest { public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" + "\n" + @@ -73,8 +76,13 @@ public abstract class AbstractCoapAttributesRequestProtoIntegrationTest extends @Before @Override public void beforeTest() throws Exception { - processBeforeTest("Test Request attribute values from the server proto", CoapDeviceType.DEFAULT, - TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestJsonSqlIntegrationTest.java deleted file mode 100644 index 10f2f008d0..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestJsonSqlIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.attributes.request.sql; - -import org.thingsboard.server.transport.coap.attributes.request.AbstractCoapAttributesRequestJsonIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapAttributesRequestJsonSqlIntegrationTest extends AbstractCoapAttributesRequestJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestProtoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestProtoSqlIntegrationTest.java deleted file mode 100644 index 915f40c651..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestProtoSqlIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.attributes.request.sql; - -import org.thingsboard.server.transport.coap.attributes.request.AbstractCoapAttributesRequestProtoIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapAttributesRequestProtoSqlIntegrationTest extends AbstractCoapAttributesRequestProtoIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestSqlIntegrationTest.java deleted file mode 100644 index 327f2158c4..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/sql/CoapAttributesRequestSqlIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.attributes.request.sql; - -import org.thingsboard.server.transport.coap.attributes.request.AbstractCoapAttributesRequestIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapAttributesRequestSqlIntegrationTest extends AbstractCoapAttributesRequestIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java similarity index 74% rename from application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java index fb2b9f55da..bbbfad3c65 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java @@ -17,18 +17,25 @@ package org.thingsboard.server.transport.coap.attributes.updates; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; +import org.awaitility.Awaitility; import org.eclipse.californium.core.CoapHandler; import org.eclipse.californium.core.CoapObserveRelation; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.server.resources.Resource; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; +import org.thingsboard.server.coapserver.DefaultCoapServerService; import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.transport.service.DefaultTransportService; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; +import org.thingsboard.server.transport.coap.CoapTransportResource; +import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; import java.nio.charset.StandardCharsets; import java.util.concurrent.CountDownLatch; @@ -37,19 +44,36 @@ import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributesIntegrationTest { +@DaoSqlTest +public class CoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributesIntegrationTest { private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}"; protected static final String POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION = "{\"attribute1\":\"value\",\"attribute2\":false,\"attribute3\":41.0,\"attribute4\":72," + "\"attribute5\":{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}}"; + CoapTransportResource coapTransportResource; + + @Autowired + DefaultCoapServerService defaultCoapServerService; + + @Autowired + DefaultTransportService defaultTransportService; + @Before public void beforeTest() throws Exception { - processBeforeTest("Test Subscribe to attribute updates", null, null); + Resource api = defaultCoapServerService.getCoapServer().getRoot().getChild("api"); + coapTransportResource = spy( (CoapTransportResource) api.getChild("v1") ); + api.delete(api.getChild("v1") ); + api.add(coapTransportResource); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .build(); + processBeforeTest(configProperties); } @After @@ -89,21 +113,23 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr } latch = new CountDownLatch(1); - + int expectedObserveCnt = callback.getObserve().intValue() + 1; doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); latch.await(3, TimeUnit.SECONDS); - validateUpdateAttributesResponse(callback); + validateUpdateAttributesResponse(callback, expectedObserveCnt); - latch = new CountDownLatch(1); + latch = new CountDownLatch(1); + int expectedObserveBeforeDeleteCnt = callback.getObserve().intValue() + 1; doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); latch.await(3, TimeUnit.SECONDS); - validateDeleteAttributesResponse(callback); - + validateDeleteAttributesResponse(callback, expectedObserveBeforeDeleteCnt); observeRelation.proactiveCancel(); assertTrue(observeRelation.isCanceled()); + + awaitClientAfterCancelObserve(); } protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { @@ -124,20 +150,20 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr assertEquals("{}", response); } - protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + protected void validateUpdateAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getObserve()); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(1, callback.getObserve().intValue()); + assertEquals(expectedObserveCnt, callback.getObserve().intValue()); String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response)); } - protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + protected void validateDeleteAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getObserve()); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(2, callback.getObserve().intValue()); + assertEquals(expectedObserveCnt, callback.getObserve().intValue()); String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response)); } @@ -180,4 +206,13 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr } } + + private void awaitClientAfterCancelObserve() { + Awaitility.await("awaitClientAfterCancelObserve") + .pollInterval(10, TimeUnit.MILLISECONDS) + .atMost(5, TimeUnit.SECONDS) + .until(()->{ + log.trace("awaiting defaultTransportService.sessions is empty"); + return defaultTransportService.sessions.isEmpty();}); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java similarity index 71% rename from application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java index 04866cd849..4ab052db46 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java @@ -21,13 +21,21 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j -public abstract class AbstractCoapAttributesUpdatesJsonIntegrationTest extends AbstractCoapAttributesUpdatesIntegrationTest { +@DaoSqlTest +public class CoapAttributesUpdatesJsonIntegrationTest extends CoapAttributesUpdatesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Subscribe to attribute updates", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @After diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java similarity index 89% rename from application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java index 3cf1884bc5..4d9970b2dc 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java @@ -23,7 +23,9 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.util.ArrayList; import java.util.List; @@ -35,11 +37,17 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertTrue; @Slf4j -public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends AbstractCoapAttributesUpdatesIntegrationTest { +@DaoSqlTest +public class CoapAttributesUpdatesProtoIntegrationTest extends CoapAttributesUpdatesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Subscribe to attribute updates", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); } @After @@ -92,11 +100,11 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends assertEquals(0, callback.getObserve().intValue()); } - protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + protected void validateUpdateAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getObserve()); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(1, callback.getObserve().intValue()); + assertEquals(expectedObserveCnt, callback.getObserve().intValue()); TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); List tsKvProtoList = getTsKvProtoList(); attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList); @@ -112,11 +120,11 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends } - protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { + protected void validateDeleteAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { assertNotNull(callback.getPayloadBytes()); assertNotNull(callback.getObserve()); assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(2, callback.getObserve().intValue()); + assertEquals(expectedObserveCnt, callback.getObserve().intValue()); TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5"); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlIntegrationTest.java deleted file mode 100644 index a1ec990043..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.attributes.updates.sql; - -import org.thingsboard.server.transport.coap.attributes.updates.AbstractCoapAttributesUpdatesIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapAttributesUpdatesSqlIntegrationTest extends AbstractCoapAttributesUpdatesIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlJsonIntegrationTest.java deleted file mode 100644 index 644160c4f4..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlJsonIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.attributes.updates.sql; - -import org.thingsboard.server.transport.coap.attributes.updates.AbstractCoapAttributesUpdatesJsonIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapAttributesUpdatesSqlJsonIntegrationTest extends AbstractCoapAttributesUpdatesJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlProtoIntegrationTest.java deleted file mode 100644 index a97f3d4210..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/sql/CoapAttributesUpdatesSqlProtoIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.attributes.updates.sql; - -import org.thingsboard.server.transport.coap.attributes.updates.AbstractCoapAttributesUpdatesProtoIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapAttributesUpdatesSqlProtoIntegrationTest extends AbstractCoapAttributesUpdatesProtoIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimDeviceTest.java similarity index 90% rename from application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimDeviceTest.java index 2562c8c21c..de9c024bb8 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimDeviceTest.java @@ -24,7 +24,6 @@ import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; @@ -33,6 +32,9 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.io.IOException; @@ -41,7 +43,8 @@ import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractCoapClaimDeviceTest extends AbstractCoapIntegrationTest { +@DaoSqlTest +public class CoapClaimDeviceTest extends AbstractCoapIntegrationTest { protected static final String CUSTOMER_USER_PASSWORD = "customerUser123!"; @@ -50,21 +53,24 @@ public abstract class AbstractCoapClaimDeviceTest extends AbstractCoapIntegratio @Before public void beforeTest() throws Exception { - super.processBeforeTest("Test Claim device", null, null); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Claim device") + .build(); + processBeforeTest(configProperties); createCustomerAndUser(); } protected void createCustomerAndUser() throws Exception { Customer customer = new Customer(); - customer.setTenantId(savedTenant.getId()); + customer.setTenantId(tenantId); customer.setTitle("Test Claiming Customer"); savedCustomer = doPost("/api/customer", customer, Customer.class); assertNotNull(savedCustomer); - assertEquals(savedTenant.getId(), savedCustomer.getTenantId()); + assertEquals(tenantId, savedCustomer.getTenantId()); User user = new User(); user.setAuthority(Authority.CUSTOMER_USER); - user.setTenantId(savedTenant.getId()); + user.setTenantId(tenantId); user.setCustomerId(savedCustomer.getId()); user.setEmail("customer@thingsboard.org"); @@ -75,7 +81,7 @@ public abstract class AbstractCoapClaimDeviceTest extends AbstractCoapIntegratio @After public void afterTest() throws Exception { - super.processAfterTest(); + processAfterTest(); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimJsonDeviceTest.java similarity index 71% rename from application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimJsonDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimJsonDeviceTest.java index 200ea3dd6d..7807dc0635 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimJsonDeviceTest.java @@ -21,13 +21,21 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j -public abstract class AbstractCoapClaimJsonDeviceTest extends AbstractCoapClaimDeviceTest { +@DaoSqlTest +public class CoapClaimJsonDeviceTest extends CoapClaimDeviceTest { @Before public void beforeTest() throws Exception { - super.processBeforeTest("Test Claim device Json", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Claim device Json") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); createCustomerAndUser(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimProtoDeviceTest.java similarity index 83% rename from application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimProtoDeviceTest.java index eadb86f862..812ea7392b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/AbstractCoapClaimProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimProtoDeviceTest.java @@ -16,21 +16,28 @@ package org.thingsboard.server.transport.coap.claim; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportApiProtos; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j -public abstract class AbstractCoapClaimProtoDeviceTest extends AbstractCoapClaimDeviceTest { +@DaoSqlTest +public class CoapClaimProtoDeviceTest extends CoapClaimDeviceTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Claim device Proto", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Claim device Proto") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); createCustomerAndUser(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceJsonSqlTest.java deleted file mode 100644 index e80c117ee3..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceJsonSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.claim.sql; - -import org.thingsboard.server.transport.coap.claim.AbstractCoapClaimJsonDeviceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapClaimDeviceJsonSqlTest extends AbstractCoapClaimJsonDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceProtoSqlTest.java deleted file mode 100644 index 9cebcc3e7a..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceProtoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.claim.sql; - -import org.thingsboard.server.transport.coap.claim.AbstractCoapClaimProtoDeviceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapClaimDeviceProtoSqlTest extends AbstractCoapClaimProtoDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceSqlTest.java deleted file mode 100644 index 38124428c9..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/sql/CoapClaimDeviceSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.claim.sql; - -import org.thingsboard.server.transport.coap.claim.AbstractCoapClaimDeviceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapClaimDeviceSqlTest extends AbstractCoapClaimDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java similarity index 69% rename from application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java index 88d663833c..a952ef6d63 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java @@ -25,6 +25,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.Device; @@ -37,13 +38,13 @@ import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.io.IOException; -import static org.junit.Assert.assertEquals; - @Slf4j -public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { +@DaoSqlTest +public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { @Autowired DeviceCredentialsService deviceCredentialsService; @@ -53,7 +54,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn @After public void afterTest() throws Exception { - super.processAfterTest(); + processAfterTest(); } @Test @@ -88,7 +89,12 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); byte[] result = createCoapClientAndPublish().getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); @@ -97,15 +103,23 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); byte[] result = createCoapClientAndPublish().getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); @@ -113,16 +127,24 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); @@ -132,16 +154,24 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); @@ -156,18 +186,34 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn } private void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); byte[] result = createCoapClientAndPublish().getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } private void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKeyOrig") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); byte[] result = createCoapClientAndPublish().getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java similarity index 72% rename from application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java index 630b46f760..a2e270796e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java @@ -24,6 +24,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.Device; @@ -43,11 +44,13 @@ import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceReque import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.io.IOException; @Slf4j -public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { +@DaoSqlTest +public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { @Autowired DeviceCredentialsService deviceCredentialsService; @@ -57,7 +60,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI @After public void afterTest() throws Exception { - super.processAfterTest(); + processAfterTest(); } @Test @@ -92,37 +95,58 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI private void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); Assert.assertNotNull(result); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); } private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayload()); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); @@ -131,16 +155,24 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI } private void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device3") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayload()); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); @@ -155,17 +187,33 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI } private void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } private void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Provision device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKeyOrig") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceJsonSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceJsonSqlTest.java deleted file mode 100644 index 8ffecffd54..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceJsonSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.provision.sql; - -import org.thingsboard.server.transport.coap.provision.AbstractCoapProvisionJsonDeviceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapProvisionDeviceJsonSqlTest extends AbstractCoapProvisionJsonDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceProtoSqlTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceProtoSqlTest.java deleted file mode 100644 index 4b80c0d662..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/sql/CoapProvisionDeviceProtoSqlTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.provision.sql; - -import org.thingsboard.server.transport.coap.provision.AbstractCoapProvisionProtoDeviceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapProvisionDeviceProtoSqlTest extends AbstractCoapProvisionProtoDeviceTest { -} 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 d598a82ccb..5262b55b97 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 @@ -26,8 +26,6 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Request; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.CoapDeviceType; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; @@ -45,12 +43,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC protected static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; - protected Long asyncContextTimeoutToUseRpcPlugin; - - protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { - super.processBeforeTest(deviceName, coapDeviceType, payloadType); - asyncContextTimeoutToUseRpcPlugin = 10000L; - } + protected static final Long asyncContextTimeoutToUseRpcPlugin = 10000L; protected void processOneWayRpcTest() throws Exception { client = getCoapClient(FeatureType.RPC); 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/CoapServerSideRpcDefaultIntegrationTest.java similarity index 83% rename from application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcDefaultIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java index ee10c467d5..cd0e9268b4 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/CoapServerSideRpcDefaultIntegrationTest.java @@ -21,21 +21,30 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.security.AccessValidator; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractCoapServerSideRpcDefaultIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { +@DaoSqlTest +public class CoapServerSideRpcDefaultIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", null, null); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("RPC test device") + .build(); + processBeforeTest(configProperties); } @After public void afterTest() throws Exception { - super.processAfterTest(); + processAfterTest(); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java similarity index 68% rename from application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java index 69e65afb4d..6dde4b919b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java @@ -21,18 +21,26 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j -public abstract class AbstractCoapServerSideRpcJsonIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { +@DaoSqlTest +public class CoapServerSideRpcJsonIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("RPC test device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @After public void afterTest() throws Exception { - super.processAfterTest(); + processAfterTest(); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java similarity index 88% rename from application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java index aa79732fde..ae1c89d765 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java @@ -23,16 +23,12 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapHandler; -import org.eclipse.californium.core.CoapObserveRelation; import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.core.coap.Request; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; @@ -40,19 +36,16 @@ import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeCo import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; -import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; -import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { +@DaoSqlTest +public class CoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { private static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + "package rpc;\n" + @@ -70,12 +63,18 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("RPC test device") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .build(); + processBeforeTest(configProperties); } @After public void afterTest() throws Exception { - super.processAfterTest(); + processAfterTest(); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcJsonSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcJsonSqlIntegrationTest.java deleted file mode 100644 index fa23838daa..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcJsonSqlIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.rpc.sql; - -import org.thingsboard.server.transport.coap.rpc.AbstractCoapServerSideRpcJsonIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapServerSideRpcJsonSqlIntegrationTest extends AbstractCoapServerSideRpcJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcSqlIntegrationTest.java deleted file mode 100644 index 3b1c12d38d..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcSqlIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.rpc.sql; - -import org.thingsboard.server.transport.coap.rpc.AbstractCoapServerSideRpcDefaultIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class CoapServerSideRpcSqlIntegrationTest extends AbstractCoapServerSideRpcDefaultIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java similarity index 94% rename from application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java index 1fae984364..ee566be2bb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.coap.telemetry.attributes; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapClient; @@ -26,9 +25,11 @@ import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.io.IOException; import java.util.Arrays; @@ -43,14 +44,18 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @Slf4j -public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { +@DaoSqlTest +public class CoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { private static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; @Before public void beforeTest() throws Exception { - processBeforeTest("Test Post Attributes device", null, null); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .build(); + processBeforeTest(configProperties); } @After diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesJsonIntegrationTest.java similarity index 67% rename from application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesJsonIntegrationTest.java index 09406debdb..38f5b872af 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesJsonIntegrationTest.java @@ -21,13 +21,21 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j -public abstract class AbstractCoapAttributesJsonIntegrationTest extends AbstractCoapAttributesIntegrationTest { +@DaoSqlTest +public class CoapAttributesJsonIntegrationTest extends CoapAttributesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Post Attributes device Json", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Attributes device Json") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @After diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesProtoIntegrationTest.java similarity index 94% rename from application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesProtoIntegrationTest.java index 0f7e63d1ac..539f62f614 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/AbstractCoapAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesProtoIntegrationTest.java @@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeCo import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.util.Arrays; @@ -37,12 +39,18 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @Slf4j -public abstract class AbstractCoapAttributesProtoIntegrationTest extends AbstractCoapAttributesIntegrationTest { +@DaoSqlTest +public class CoapAttributesProtoIntegrationTest extends CoapAttributesIntegrationTest { @Before @Override public void beforeTest() throws Exception { - processBeforeTest("Test Post Attributes device Proto", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Attributes device Proto") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlIntegrationTest.java deleted file mode 100644 index 6bea4fb8df..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlIntegrationTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.telemetry.attributes.sql; - -import org.thingsboard.server.transport.coap.telemetry.attributes.AbstractCoapAttributesIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ -@DaoSqlTest -public class CoapAttributesSqlIntegrationTest extends AbstractCoapAttributesIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlJsonIntegrationTest.java deleted file mode 100644 index 79d6eaf7c9..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlJsonIntegrationTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.telemetry.attributes.sql; - -import org.thingsboard.server.transport.coap.telemetry.attributes.AbstractCoapAttributesJsonIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ -@DaoSqlTest -public class CoapAttributesSqlJsonIntegrationTest extends AbstractCoapAttributesJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlProtoIntegrationTest.java deleted file mode 100644 index 8d7b56f54d..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/sql/CoapAttributesSqlProtoIntegrationTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2022 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.coap.telemetry.attributes.sql; - -import org.thingsboard.server.transport.coap.telemetry.attributes.AbstractCoapAttributesProtoIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ -@DaoSqlTest -public class CoapAttributesSqlProtoIntegrationTest extends AbstractCoapAttributesProtoIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java index 436288ae1f..0c7ab4d1c5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java @@ -25,8 +25,9 @@ import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.io.IOException; import java.util.Arrays; @@ -46,7 +47,10 @@ public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoap @Before public void beforeTest() throws Exception { - processBeforeTest("Test Post Telemetry device", null, null); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Telemetry device") + .build(); + processBeforeTest(configProperties); } @After diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesJsonIntegrationTest.java index 7241e25106..bfb8b24bf7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesJsonIntegrationTest.java @@ -21,13 +21,19 @@ import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j public abstract class AbstractCoapTimeseriesJsonIntegrationTest extends AbstractCoapTimeseriesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Post Telemetry device json payload", CoapDeviceType.DEFAULT, TransportPayloadType.JSON); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @After diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java index 374555ed81..93e132663e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeCo import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.util.Arrays; @@ -48,7 +49,12 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac @Test public void testPushTelemetry() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; @@ -117,7 +123,13 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac " }\n" + " }\n" + "}"; - processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryProtoSchema(schemaStr) + .build(); + processBeforeTest(configProperties); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; @@ -172,7 +184,12 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac @Test public void testPushTelemetryWithExplicitPresenceProtoKeys() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; @@ -239,7 +256,13 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac " }\n" + " }\n" + "}"; - processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); + CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .coapDeviceType(CoapDeviceType.DEFAULT) + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryProtoSchema(schemaStr) + .build(); + processBeforeTest(configProperties); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; 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 9f0c18b449..20aa0a1805 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.junit.Assert; import org.junit.Before; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.TestPropertySource; import org.springframework.util.SocketUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -59,8 +60,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.controller.AbstractWebsocketTest; -import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; @@ -98,9 +98,12 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClient import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; +@TestPropertySource(properties = { + "transport.lwm2m.enabled=true", +}) @Slf4j @DaoSqlTest -public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { +public abstract class AbstractLwM2MIntegrationTest extends AbstractControllerTest { @SpyBean DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; @@ -172,7 +175,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest protected final Set expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); protected DeviceProfile deviceProfile; protected ScheduledExecutorService executor; - protected TbTestWebSocketClient wsClient; protected LwM2MTestClient lwM2MTestClient; private String[] resources; @@ -183,17 +185,16 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest @After public void after() { - wsClient.close(); clientDestroy(); executor.shutdownNow(); } @AfterClass - public static void afterClass () { + public static void afterClass() { awaitServersDestroy(); } - private void init () throws Exception { + private void init() throws Exception { executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled")); loginTenantAdmin(); for (String resourceName : this.resources) { @@ -208,7 +209,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest }); Assert.assertNotNull(lwModel); } - wsClient = buildAndConnectWebSocketClient(); } public void basicTestConnectionObserveTelemetry(Security security, @@ -230,12 +230,12 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); wrapper.setEntityDataCmds(Collections.singletonList(cmd)); - wsClient.send(mapper.writeValueAsString(wrapper)); - wsClient.waitForReply(); + getWsClient().send(mapper.writeValueAsString(wrapper)); + getWsClient().waitForReply(); - wsClient.registerWaitForUpdate(); + getWsClient().registerWaitForUpdate(); createNewClient(security, coapConfig, false, endpoint, false, null); - String msg = wsClient.waitForUpdate(); + String msg = getWsClient().waitForUpdate(); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); Assert.assertEquals(1, update.getCmdId()); @@ -367,7 +367,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest return credentials; } - private static void awaitServersDestroy() { + private static void awaitServersDestroy() { await("One of servers ports number is not free") .atMost(3000, TimeUnit.MILLISECONDS) .until(() -> isServerPortsAvailable() == null); @@ -386,7 +386,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest return null; } - private static void awaitClientDestroy(LeshanClient leshanClient) { + private static void awaitClientDestroy(LeshanClient leshanClient) { await("Destroy LeshanClient: delete All is registered Servers.") .atMost(2000, TimeUnit.MILLISECONDS) .until(() -> leshanClient.getRegisteredServers().size() == 0); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java index 8173b39b5c..f7d1303674 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java @@ -52,8 +52,6 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfil @Slf4j public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { - public static final int TIMEOUT = 30; - protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA = " {\n" + diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index 88c1b5f5a1..deb0574704 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -22,16 +22,15 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; -import org.junit.Assert; +import org.springframework.test.context.TestPropertySource; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; @@ -42,7 +41,6 @@ import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadCon import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.AbstractTransportIntegrationTest; @@ -51,98 +49,37 @@ import java.util.List; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +@TestPropertySource(properties = { + "transport.mqtt.enabled=true", +}) @Slf4j public abstract class AbstractMqttIntegrationTest extends AbstractTransportIntegrationTest { protected Device savedGateway; protected String gatewayAccessToken; - protected DeviceProfile deviceProfile; - - protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { - this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); - } - - protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic, boolean enableCompatibilityWithJsonPayloadFormat, boolean useJsonPayloadFormatForDefaultDownlinkTopics) throws Exception { - this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics); - } - - protected void processBeforeTest(String deviceName, - String gatewayName, - TransportPayloadType payloadType, - String telemetryTopic, - String attributesTopic, - String telemetryProtoSchema, - String attributesProtoSchema, - String rpcResponseProtoSchema, - String rpcRequestProtoSchema, - String provisionKey, - String provisionSecret, - DeviceProfileProvisionType provisionType, - boolean enableCompatibilityWithJsonPayloadFormat, - boolean useJsonPayloadFormatForDefaultDownlinkTopics) throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - - Device device = new Device(); - device.setName(deviceName); - device.setType("default"); - - Device gateway = new Device(); - gateway.setName(gatewayName); - gateway.setType("default"); - ObjectNode additionalInfo = mapper.createObjectNode(); - additionalInfo.put("gateway", true); - gateway.setAdditionalInfo(additionalInfo); - - if (payloadType != null) { - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema, provisionKey, provisionSecret, provisionType, enableCompatibilityWithJsonPayloadFormat, useJsonPayloadFormatForDefaultDownlinkTopics); - deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); - device.setType(deviceProfile.getName()); - device.setDeviceProfileId(deviceProfile.getId()); - gateway.setType(deviceProfile.getName()); - gateway.setDeviceProfileId(deviceProfile.getId()); + protected void processBeforeTest(MqttTestConfigProperties config) throws Exception { + loginTenantAdmin(); + deviceProfile = createMqttDeviceProfile(config); + assertNotNull(deviceProfile); + if (config.getDeviceName() != null) { + savedDevice = createDevice(config.getDeviceName(), deviceProfile.getName(), false); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); + assertNotNull(deviceCredentials); + assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); + accessToken = deviceCredentials.getCredentialsId(); + assertNotNull(accessToken); } - - savedDevice = doPost("/api/device", device, Device.class); - - DeviceCredentials deviceCredentials = - doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); - - savedGateway = doPost("/api/device", gateway, Device.class); - - DeviceCredentials gatewayCredentials = - doGet("/api/device/" + savedGateway.getId().getId().toString() + "/credentials", DeviceCredentials.class); - - assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); - accessToken = deviceCredentials.getCredentialsId(); - assertNotNull(accessToken); - - assertEquals(savedGateway.getId(), gatewayCredentials.getDeviceId()); - gatewayAccessToken = gatewayCredentials.getCredentialsId(); - assertNotNull(gatewayAccessToken); - - } - - protected void processAfterTest() throws Exception { - loginSysAdmin(); - if (savedTenant != null) { - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); + if (config.getGatewayName() != null) { + savedGateway = createDevice(config.getGatewayName(), deviceProfile.getName(), true); + DeviceCredentials gatewayCredentials = + doGet("/api/device/" + savedGateway.getId().getId().toString() + "/credentials", DeviceCredentials.class); + assertNotNull(gatewayCredentials); + assertEquals(savedGateway.getId(), gatewayCredentials.getDeviceId()); + gatewayAccessToken = gatewayCredentials.getCredentialsId(); + assertNotNull(gatewayAccessToken); } } @@ -162,76 +99,95 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg client.publish(topic, message); } - protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, - String telemetryTopic, String attributesTopic, - String telemetryProtoSchema, String attributesProtoSchema, - String rpcResponseProtoSchema, String rpcRequestProtoSchema, - String provisionKey, String provisionSecret, - DeviceProfileProvisionType provisionType, - boolean enableCompatibilityWithJsonPayloadFormat, - boolean useJsonPayloadFormatForDefaultDownlinkTopics) { - DeviceProfile deviceProfile = new DeviceProfile(); - deviceProfile.setName(transportPayloadType.name()); - deviceProfile.setType(DeviceProfileType.DEFAULT); - deviceProfile.setTransportType(DeviceTransportType.MQTT); - deviceProfile.setProvisionType(provisionType); - deviceProfile.setProvisionDeviceKey(provisionKey); - deviceProfile.setDescription(transportPayloadType.name() + " Test"); - DeviceProfileData deviceProfileData = new DeviceProfileData(); - DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); - MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); - if (!StringUtils.isEmpty(telemetryTopic)) { - mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(telemetryTopic); - } - if (!StringUtils.isEmpty(attributesTopic)) { - mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(attributesTopic); - } - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; - if (TransportPayloadType.JSON.equals(transportPayloadType)) { - transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); + protected DeviceProfile createMqttDeviceProfile(MqttTestConfigProperties config) throws Exception { + TransportPayloadType transportPayloadType = config.getTransportPayloadType(); + if (transportPayloadType == null) { + DeviceProfileInfo defaultDeviceProfileInfo = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class); + return doGet("/api/deviceProfile/" + defaultDeviceProfileInfo.getId().getId(), DeviceProfile.class); } else { - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); - if (StringUtils.isEmpty(telemetryProtoSchema)) { - telemetryProtoSchema = DEVICE_TELEMETRY_PROTO_SCHEMA; + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setName(transportPayloadType.name()); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTransportType(DeviceTransportType.MQTT); + DeviceProfileProvisionType provisionType = config.getProvisionType() != null ? + config.getProvisionType() : DeviceProfileProvisionType.DISABLED; + deviceProfile.setProvisionType(provisionType); + deviceProfile.setProvisionDeviceKey(config.getProvisionKey()); + deviceProfile.setDescription(transportPayloadType.name() + " Test"); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); + MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration(); + if (StringUtils.hasLength(config.getTelemetryTopicFilter())) { + mqttDeviceProfileTransportConfiguration.setDeviceTelemetryTopic(config.getTelemetryTopicFilter()); } - if (StringUtils.isEmpty(attributesProtoSchema)) { - attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; + if (StringUtils.hasLength(config.getAttributesTopicFilter())) { + mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(config.getAttributesTopicFilter()); } - if (StringUtils.isEmpty(rpcResponseProtoSchema)) { - rpcResponseProtoSchema = DEVICE_RPC_RESPONSE_PROTO_SCHEMA; + mqttDeviceProfileTransportConfiguration.setSendAckOnValidationException(config.isSendAckOnValidationException()); + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; + if (TransportPayloadType.JSON.equals(transportPayloadType)) { + transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); + } else { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = new ProtoTransportPayloadConfiguration(); + String telemetryProtoSchema = config.getTelemetryProtoSchema(); + String attributesProtoSchema = config.getAttributesProtoSchema(); + String rpcResponseProtoSchema = config.getRpcResponseProtoSchema(); + String rpcRequestProtoSchema = config.getRpcRequestProtoSchema(); + protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema( + telemetryProtoSchema != null ? telemetryProtoSchema : DEVICE_TELEMETRY_PROTO_SCHEMA + ); + protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema( + attributesProtoSchema != null ? attributesProtoSchema : DEVICE_ATTRIBUTES_PROTO_SCHEMA + ); + protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema( + rpcResponseProtoSchema != null ? rpcResponseProtoSchema : DEVICE_RPC_RESPONSE_PROTO_SCHEMA + ); + protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema( + rpcRequestProtoSchema != null ? rpcRequestProtoSchema : DEVICE_RPC_REQUEST_PROTO_SCHEMA + ); + protoTransportPayloadConfiguration.setEnableCompatibilityWithJsonPayloadFormat( + config.isEnableCompatibilityWithJsonPayloadFormat() + ); + protoTransportPayloadConfiguration.setUseJsonPayloadFormatForDefaultDownlinkTopics( + config.isEnableCompatibilityWithJsonPayloadFormat() && + config.isUseJsonPayloadFormatForDefaultDownlinkTopics() + ); + transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; } - if (StringUtils.isEmpty(rpcRequestProtoSchema)) { - rpcRequestProtoSchema = DEVICE_RPC_REQUEST_PROTO_SCHEMA; + mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + deviceProfileData.setTransportConfiguration(mqttDeviceProfileTransportConfiguration); + DeviceProfileProvisionConfiguration provisionConfiguration; + switch (provisionType) { + case ALLOW_CREATE_NEW_DEVICES: + provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); + break; + case CHECK_PRE_PROVISIONED_DEVICES: + provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(config.getProvisionSecret()); + break; + case DISABLED: + default: + provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(config.getProvisionSecret()); + break; } - protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); - protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); - protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema); - protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema); - protoTransportPayloadConfiguration.setEnableCompatibilityWithJsonPayloadFormat(enableCompatibilityWithJsonPayloadFormat); - protoTransportPayloadConfiguration.setUseJsonPayloadFormatForDefaultDownlinkTopics(enableCompatibilityWithJsonPayloadFormat && useJsonPayloadFormatForDefaultDownlinkTopics); - transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; + deviceProfileData.setProvisionConfiguration(provisionConfiguration); + deviceProfileData.setConfiguration(configuration); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setDefault(false); + deviceProfile.setDefaultRuleChainId(null); + return doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); } - mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); - deviceProfileData.setTransportConfiguration(mqttDeviceProfileTransportConfiguration); - DeviceProfileProvisionConfiguration provisionConfiguration; - switch (provisionType) { - case ALLOW_CREATE_NEW_DEVICES: - provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(provisionSecret); - break; - case CHECK_PRE_PROVISIONED_DEVICES: - provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(provisionSecret); - break; - case DISABLED: - default: - provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(provisionSecret); - break; + } + + protected Device createDevice(String name, String type, boolean gateway) throws Exception { + Device device = new Device(); + device.setName(name); + device.setType(type); + if (gateway) { + ObjectNode additionalInfo = mapper.createObjectNode(); + additionalInfo.put("gateway", true); + device.setAdditionalInfo(additionalInfo); } - deviceProfileData.setProvisionConfiguration(provisionConfiguration); - deviceProfileData.setConfiguration(configuration); - deviceProfile.setProfileData(deviceProfileData); - deviceProfile.setDefault(false); - deviceProfile.setDefaultRuleChainId(null); - return deviceProfile; + return doPost("/api/device", device, Device.class); } protected TransportProtos.PostAttributeMsg getPostAttributeMsg(List expectedKeys) { diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java new file mode 100644 index 0000000000..bc535b424b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2022 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 lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.TransportPayloadType; + +@Data +@Builder +public class MqttTestConfigProperties { + + String deviceName; + String gatewayName; + + TransportPayloadType transportPayloadType; + + String telemetryTopicFilter; + String attributesTopicFilter; + + String telemetryProtoSchema; + String attributesProtoSchema; + String rpcResponseProtoSchema; + String rpcRequestProtoSchema; + + boolean enableCompatibilityWithJsonPayloadFormat; + boolean useJsonPayloadFormatForDefaultDownlinkTopics; + boolean sendAckOnValidationException; + + DeviceProfileProvisionType provisionType; + String provisionKey; + String provisionSecret; + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java index 9c6b97bd59..530e67a625 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java @@ -81,14 +81,6 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}"; - protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { - super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic); - } - - protected void processAfterTest() throws Exception { - super.processAfterTest(); - } - protected List getTsKvProtoList() { TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V); TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java similarity index 56% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java index b7878154be..f2bacc66ed 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java @@ -16,62 +16,95 @@ package org.thingsboard.server.transport.mqtt.attributes.request; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Test; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.util.ArrayList; import java.util.List; @Slf4j -public abstract class AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest { - - @After - public void afterTest() throws Exception { - processAfterTest(); - } +@DaoSqlTest +public class MqttAttributesRequestBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest { @Test public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibility() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", - TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .enableCompatibilityWithJsonPayloadFormat(true) + .build(); + processBeforeTest(configProperties); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); } @Test public void testRequestAttributesValuesFromTheServerWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", - TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + processBeforeTest(configProperties); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); } @Test public void testRequestAttributesValuesFromTheServerOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", - TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + processBeforeTest(configProperties); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX); } @Test public void testRequestAttributesValuesFromTheServerOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", - TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, true, true); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + processBeforeTest(configProperties); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX); } @Test public void testRequestAttributesValuesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null, true, true); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .gatewayName("Gateway Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + processBeforeTest(configProperties); processProtoTestGatewayRequestAttributesValuesFromTheServer(); } @Test public void testRequestAttributesValuesFromTheServerOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null, true, true); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .gatewayName("Gateway Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + processBeforeTest(configProperties); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestIntegrationTest.java similarity index 79% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestIntegrationTest.java index 67a4b0a386..c4da6b3184 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestIntegrationTest.java @@ -16,24 +16,24 @@ package org.thingsboard.server.transport.mqtt.attributes.request; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; @Slf4j -public abstract class AbstractMqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest { +@DaoSqlTest +public class MqttAttributesRequestIntegrationTest extends AbstractMqttAttributesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Request attribute values from the server json", "Gateway Test Request attribute values from the server json", TransportPayloadType.JSON, null, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server") + .gatewayName("Gateway Test Request attribute values from the server") + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestJsonIntegrationTest.java similarity index 53% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestJsonIntegrationTest.java index 4738c63592..a27c9391fd 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestJsonIntegrationTest.java @@ -15,53 +15,27 @@ */ package org.thingsboard.server.transport.mqtt.attributes.request; -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import io.netty.handler.codec.mqtt.MqttQoS; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; -import org.thingsboard.server.gen.transport.TransportApiProtos; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @Slf4j -public abstract class AbstractMqttAttributesRequestIntegrationTest extends AbstractMqttAttributesIntegrationTest { +@DaoSqlTest +public class MqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Request attribute values from the server", "Gateway Test Request attribute values from the server", null, null, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server json") + .gatewayName("Gateway Test Request attribute values from the server json") + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestProtoIntegrationTest.java similarity index 64% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestProtoIntegrationTest.java index 4c88cbbaec..402d9a7263 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/MqttAttributesRequestProtoIntegrationTest.java @@ -16,55 +16,72 @@ package org.thingsboard.server.transport.mqtt.attributes.request; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Test; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; import java.util.ArrayList; import java.util.List; @Slf4j -public abstract class AbstractMqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest { - - @After - public void afterTest() throws Exception { - processAfterTest(); - } +@DaoSqlTest +public class MqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest { @Test public void testRequestAttributesValuesFromTheServer() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", - TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .build(); + processBeforeTest(configProperties); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); } @Test public void testRequestAttributesValuesFromTheServerOnShortTopic() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", - TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .build(); + processBeforeTest(configProperties); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_TOPIC_PREFIX); } @Test public void testRequestAttributesValuesFromTheServerOnShortProtoTopic() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", - TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesProtoSchema(ATTRIBUTES_SCHEMA_STR) + .build(); + processBeforeTest(configProperties); processProtoTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_PROTO_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_PROTO_TOPIC_PREFIX); } @Test public void testRequestAttributesValuesFromTheServerGateway() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .gatewayName("Gateway Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); processProtoTestGatewayRequestAttributesValuesFromTheServer(); } @Test public void testRequestAttributesValuesFromTheServerOnShortJsonTopic() throws Exception { - super.processBeforeTest("Test Request attribute values from the server proto", "Gateway Test Request attribute values from the server proto", TransportPayloadType.PROTOBUF, null, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Request attribute values from the server proto") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); processJsonTestRequestAttributesValuesFromTheServer(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_SHORT_JSON_TOPIC, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_SHORT_JSON_TOPIC_PREFIX); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java deleted file mode 100644 index ea300c978c..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestBackwardCompatibilityIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.request.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest; - -@DaoSqlTest -public class MqttAttributesRequestBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesRequestBackwardCompatibilityIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestIntegrationTest.java deleted file mode 100644 index a96ca0f64a..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.request.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestIntegrationTest; - -@DaoSqlTest -public class MqttAttributesRequestIntegrationTest extends AbstractMqttAttributesRequestIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonIntegrationTest.java deleted file mode 100644 index d41a3d9aed..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestJsonIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.request.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestJsonIntegrationTest; - -@DaoSqlTest -public class MqttAttributesRequestJsonIntegrationTest extends AbstractMqttAttributesRequestJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoIntegrationTest.java deleted file mode 100644 index 2e5d15c27a..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/sql/MqttAttributesRequestProtoIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.request.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.request.AbstractMqttAttributesRequestProtoIntegrationTest; - -@DaoSqlTest -public class MqttAttributesRequestProtoIntegrationTest extends AbstractMqttAttributesRequestProtoIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java deleted file mode 100644 index b621698fc2..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.updates; - -import lombok.extern.slf4j.Slf4j; -import org.junit.After; -import org.junit.Test; -import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; - -@Slf4j -public abstract class AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest { - - @After - public void afterTest() throws Exception { - processAfterTest(); - } - - @Test - public void testSubscribeToAttributesUpdatesFromServerWithEnabledJsonCompatibility() throws Exception { - super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, false); - processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); - } - - @Test - public void testSubscribeToAttributesUpdatesFromServerWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true); - processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); - } - - @Test - public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true); - processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC); - } - - @Test - public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true); - processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC); - } - - @Test - public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, true); - processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC); - } - - @Test - public void testProtoSubscribeToAttributesUpdatesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null, true, false); - processProtoGatewayTestSubscribeToAttributesUpdates(); - } - -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java new file mode 100644 index 0000000000..c79f695fb6 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java @@ -0,0 +1,102 @@ +/** + * Copyright © 2016-2022 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.attributes.updates; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Test; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; +import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; + +@Slf4j +@DaoSqlTest +public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesIntegrationTest { + + @Test + public void testSubscribeToAttributesUpdatesFromServerWithEnabledJsonCompatibility() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .build(); + processBeforeTest(configProperties); + processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); + } + + @Test + public void testSubscribeToAttributesUpdatesFromServerWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_TOPIC); + } + + @Test + public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC); + } + + @Test + public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processJsonTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC); + } + + @Test + public void testProtoSubscribeToAttributesUpdatesFromTheServerOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processProtoTestSubscribeToAttributesUpdates(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC); + } + + @Test + public void testProtoSubscribeToAttributesUpdatesFromTheServerGatewayWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .gatewayName("Gateway Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .build(); + super.processBeforeTest(configProperties); + processProtoGatewayTestSubscribeToAttributesUpdates(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesIntegrationTest.java similarity index 75% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesIntegrationTest.java index 907b47345a..04224321d5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesIntegrationTest.java @@ -16,24 +16,26 @@ package org.thingsboard.server.transport.mqtt.attributes.updates; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; @Slf4j -public abstract class AbstractMqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest { +@DaoSqlTest +public class MqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .gatewayName("Gateway Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesJsonIntegrationTest.java similarity index 75% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesJsonIntegrationTest.java index 7c671bb2aa..36d79602ec 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesJsonIntegrationTest.java @@ -16,24 +16,26 @@ package org.thingsboard.server.transport.mqtt.attributes.updates; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; @Slf4j -public abstract class AbstractMqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest { +@DaoSqlTest +public class MqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.JSON, null, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .gatewayName("Gateway Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesProtoIntegrationTest.java similarity index 73% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesProtoIntegrationTest.java index 449397f7ae..868707ee70 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/AbstractMqttAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/MqttAttributesUpdatesProtoIntegrationTest.java @@ -15,35 +15,27 @@ */ package org.thingsboard.server.transport.mqtt.attributes.updates; -import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.gen.transport.TransportApiProtos; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import org.thingsboard.server.transport.mqtt.attributes.AbstractMqttAttributesIntegrationTest; -import java.util.List; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; - @Slf4j -public abstract class AbstractMqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest { +@DaoSqlTest +public class MqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Subscribe to attribute updates", "Gateway Test Subscribe to attribute updates", TransportPayloadType.PROTOBUF, null, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Subscribe to attribute updates") + .gatewayName("Gateway Test Subscribe to attribute updates") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java deleted file mode 100644 index e021415236..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesBackwardCompatibilityIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.updates.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest; - -@DaoSqlTest -public class MqttAttributesUpdatesBackwardCompatibilityIntegrationTest extends AbstractMqttAttributesUpdatesBackwardCompatibilityIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesIntegrationTest.java deleted file mode 100644 index b64da69a49..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.updates.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesIntegrationTest; - -@DaoSqlTest -public class MqttAttributesUpdatesIntegrationTest extends AbstractMqttAttributesUpdatesIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesJsonIntegrationTest.java deleted file mode 100644 index 8135837c74..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesJsonIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.updates.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesJsonIntegrationTest; - -@DaoSqlTest -public class MqttAttributesUpdatesJsonIntegrationTest extends AbstractMqttAttributesUpdatesJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesProtoIntegrationTest.java deleted file mode 100644 index 4630464f22..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/updates/sql/MqttAttributesUpdatesProtoIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.attributes.updates.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.attributes.updates.AbstractMqttAttributesUpdatesProtoIntegrationTest; - -@DaoSqlTest -public class MqttAttributesUpdatesProtoIntegrationTest extends AbstractMqttAttributesUpdatesProtoIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimBackwardCompatibilityDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimBackwardCompatibilityDeviceTest.java similarity index 68% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimBackwardCompatibilityDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimBackwardCompatibilityDeviceTest.java index f4e539b51a..e7670b5170 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimBackwardCompatibilityDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimBackwardCompatibilityDeviceTest.java @@ -16,23 +16,29 @@ package org.thingsboard.server.transport.mqtt.claim; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; @Slf4j -public abstract class AbstractMqttClaimBackwardCompatibilityDeviceTest extends AbstractMqttClaimDeviceTest { +@DaoSqlTest +public class MqttClaimBackwardCompatibilityDeviceTest extends MqttClaimDeviceTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.PROTOBUF, null, null, true, true); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Claim device") + .gatewayName("Test Claim gateway") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + processBeforeTest(configProperties); createCustomerAndUser(); } - @After - public void afterTest() throws Exception { super.afterTest(); } - @Test public void testGatewayClaimingDevice() throws Exception { processTestGatewayClaimingDevice("Test claiming gateway device Proto", false); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimDeviceTest.java similarity index 93% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimDeviceTest.java index 9086c537e7..d9ef8c3d09 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimDeviceTest.java @@ -24,20 +24,24 @@ import org.junit.Test; import org.thingsboard.server.common.data.ClaimRequest; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j -public abstract class AbstractMqttClaimDeviceTest extends AbstractMqttIntegrationTest { +@DaoSqlTest +public class MqttClaimDeviceTest extends AbstractMqttIntegrationTest { protected static final String CUSTOMER_USER_PASSWORD = "customerUser123!"; @@ -46,21 +50,25 @@ public abstract class AbstractMqttClaimDeviceTest extends AbstractMqttIntegratio @Before public void beforeTest() throws Exception { - super.processBeforeTest("Test Claim device", "Test Claim gateway", null, null, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Claim device") + .gatewayName("Test Claim gateway") + .build(); + processBeforeTest(configProperties); createCustomerAndUser(); } protected void createCustomerAndUser() throws Exception { Customer customer = new Customer(); - customer.setTenantId(savedTenant.getId()); + customer.setTenantId(tenantId); customer.setTitle("Test Claiming Customer"); savedCustomer = doPost("/api/customer", customer, Customer.class); assertNotNull(savedCustomer); - assertEquals(savedTenant.getId(), savedCustomer.getTenantId()); + assertEquals(tenantId, savedCustomer.getTenantId()); User user = new User(); user.setAuthority(Authority.CUSTOMER_USER); - user.setTenantId(savedTenant.getId()); + user.setTenantId(tenantId); user.setCustomerId(savedCustomer.getId()); user.setEmail("customer@thingsboard.org"); @@ -69,11 +77,6 @@ public abstract class AbstractMqttClaimDeviceTest extends AbstractMqttIntegratio assertEquals(customerAdmin.getCustomerId(), savedCustomer.getId()); } - @After - public void afterTest() throws Exception { - super.processAfterTest(); - } - @Test public void testClaimingDevice() throws Exception { processTestClaimingDevice(false); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimJsonDeviceTest.java similarity index 74% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimJsonDeviceTest.java index e020ac445d..05d4974f9d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimJsonDeviceTest.java @@ -16,25 +16,27 @@ package org.thingsboard.server.transport.mqtt.claim; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; @Slf4j -public abstract class AbstractMqttClaimJsonDeviceTest extends AbstractMqttClaimDeviceTest { +@DaoSqlTest +public class MqttClaimJsonDeviceTest extends MqttClaimDeviceTest { @Before public void beforeTest() throws Exception { - super.processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.JSON, null, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Claim device") + .gatewayName("Test Claim gateway") + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); createCustomerAndUser(); } - @After - public void afterTest() throws Exception { - super.afterTest(); - } - @Test public void testClaimingDevice() throws Exception { processTestClaimingDevice(false); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimProtoDeviceTest.java similarity index 84% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimProtoDeviceTest.java index 4dcbbb5b26..3d60e1ede5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/AbstractMqttClaimProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/MqttClaimProtoDeviceTest.java @@ -17,24 +17,28 @@ package org.thingsboard.server.transport.mqtt.claim; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportApiProtos; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; @Slf4j -public abstract class AbstractMqttClaimProtoDeviceTest extends AbstractMqttClaimDeviceTest { +@DaoSqlTest +public class MqttClaimProtoDeviceTest extends MqttClaimDeviceTest { @Before public void beforeTest() throws Exception { - processBeforeTest("Test Claim device", "Test Claim gateway", TransportPayloadType.PROTOBUF, null, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Claim device") + .gatewayName("Test Claim gateway") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); createCustomerAndUser(); } - @After - public void afterTest() throws Exception { super.afterTest(); } - @Test public void testClaimingDevice() throws Exception { processTestClaimingDevice(false); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceBackwardCompatibilityTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceBackwardCompatibilityTest.java deleted file mode 100644 index 7032c661dd..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceBackwardCompatibilityTest.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright © 2016-2022 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.claim.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimBackwardCompatibilityDeviceTest; -import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimDeviceTest; - -@DaoSqlTest -public class MqttClaimDeviceBackwardCompatibilityTest extends AbstractMqttClaimBackwardCompatibilityDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonTest.java deleted file mode 100644 index d48f7ec8b1..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceJsonTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.claim.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimJsonDeviceTest; - -@DaoSqlTest -public class MqttClaimDeviceJsonTest extends AbstractMqttClaimJsonDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoTest.java deleted file mode 100644 index 2def5f2ee0..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/claim/sql/MqttClaimDeviceProtoTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.claim.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.claim.AbstractMqttClaimProtoDeviceTest; - -@DaoSqlTest -public class MqttClaimDeviceProtoTest extends AbstractMqttClaimProtoDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/sql/BasicMqttCredentialsTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java similarity index 89% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/sql/BasicMqttCredentialsTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java index 8070781634..107d8dd0fa 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/sql/BasicMqttCredentialsTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/credentials/BasicMqttCredentialsTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.mqtt.credentials.sql; +package org.thingsboard.server.transport.mqtt.credentials; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.RandomStringUtils; @@ -22,18 +22,13 @@ import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttSecurityException; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; -import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -68,21 +63,7 @@ public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest { @Before public void before() throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant" + atomicInteger.getAndIncrement() + "@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + loginTenantAdmin(); BasicMqttCredentials credValue = new BasicMqttCredentials(); credValue.setClientId(CLIENT_ID); @@ -168,11 +149,6 @@ public class BasicMqttCredentialsTest extends AbstractMqttIntegrationTest { client.disconnect().waitForCompletion(); } - @After - public void after() throws Exception { - processAfterTest(); - } - protected MqttAsyncClient getMqttAsyncClient(String clientId, String username, String password) throws MqttException { if (StringUtils.isEmpty(clientId)) { clientId = MqttAsyncClient.generateClientId(); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionJsonDeviceTest.java similarity index 73% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionJsonDeviceTest.java index 876b40992a..c7e0156372 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionJsonDeviceTest.java @@ -22,10 +22,10 @@ import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -37,14 +37,16 @@ import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; -import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @Slf4j -public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { +@DaoSqlTest +public class MqttProvisionJsonDeviceTest extends AbstractMqttIntegrationTest { @Autowired DeviceCredentialsService deviceCredentialsService; @@ -52,11 +54,6 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn @Autowired DeviceService deviceService; - @After - public void afterTest() throws Exception { - super.processAfterTest(); - } - @Test public void testProvisioningDisabledDevice() throws Exception { processTestProvisioningDisabledDevice(); @@ -94,7 +91,12 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.DISABLED) + .build(); + super.processBeforeTest(configProperties); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); @@ -103,15 +105,22 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); @@ -119,16 +128,23 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); @@ -138,16 +154,23 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); @@ -163,16 +186,23 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); String requestCredentials = ",\"credentialsType\": \"MQTT_BASIC\",\"clientId\": \"test_clientId\",\"username\": \"test_username\",\"password\": \"test_password\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "MQTT_BASIC"); @@ -188,18 +218,32 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device") + .transportPayloadType(TransportPayloadType.JSON) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKeyOrig") + .provisionSecret("testProvisionSecret") + .build(); + super.processBeforeTest(configProperties); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionProtoDeviceTest.java similarity index 76% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionProtoDeviceTest.java index e05b5a82b3..335f7d7d6c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/MqttProvisionProtoDeviceTest.java @@ -21,10 +21,10 @@ import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; @@ -36,7 +36,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; -import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsDataProto; import org.thingsboard.server.gen.transport.TransportProtos.CredentialsType; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceCredentialsMsg; @@ -46,12 +46,14 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @Slf4j -public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { +@DaoSqlTest +public class MqttProvisionProtoDeviceTest extends AbstractMqttIntegrationTest { @Autowired DeviceCredentialsService deviceCredentialsService; @@ -59,11 +61,6 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI @Autowired DeviceService deviceService; - @After - public void afterTest() throws Exception { - super.processAfterTest(); - } - @Test public void testProvisioningDisabledDevice() throws Exception { processTestProvisioningDisabledDevice(); @@ -101,37 +98,56 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI protected void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.DISABLED) + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertNotNull(result); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.ACCESS_TOKEN); @@ -140,16 +156,23 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.X509_CERTIFICATE); @@ -164,7 +187,14 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device3") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( ValidateBasicMqttCredRequestMsg.newBuilder() .setClientId("test_clientId") @@ -175,11 +205,11 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.MQTT_BASIC, requestCredentials)).getPayloadBytes()); - Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); + Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); Assert.assertNotNull(createdDevice); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), createdDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(deviceCredentials.getCredentialsType(), DeviceCredentialsType.MQTT_BASIC); @@ -195,17 +225,31 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKey") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Provision device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .provisionType(DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES) + .provisionKey("testProvisionKeyOrig") + .provisionSecret("testProvisionSecret") + .build(); + processBeforeTest(configProperties); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonTest.java deleted file mode 100644 index 1b94ec6cbc..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceJsonTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.provision.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionJsonDeviceTest; - -@DaoSqlTest -public class MqttProvisionDeviceJsonTest extends AbstractMqttProvisionJsonDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoTest.java deleted file mode 100644 index 47d5d02e4a..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/sql/MqttProvisionDeviceProtoTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.provision.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.provision.AbstractMqttProvisionProtoDeviceTest; - -@DaoSqlTest -public class MqttProvisionDeviceProtoTest extends AbstractMqttProvisionProtoDeviceTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest.java deleted file mode 100644 index 82757801aa..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright © 2016-2022 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.rpc; - -import lombok.extern.slf4j.Slf4j; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttTopics; - -@Slf4j -public abstract class AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { - - @After - public void afterTest() throws Exception { - super.processAfterTest(); - } - - @Test - public void testServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); - } - - @Test - public void testServerMqttOneWayRpcOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); - } - - @Test - public void testServerMqttOneWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); - } - - @Test - public void testServerMqttTwoWayRpcWithEnabledJsonCompatibility() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, false); - processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); - } - - @Test - public void testServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); - } - - @Test - public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); - } - - @Test - public void testServerMqttTwoWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); - } - - @Test - public void testServerMqttTwoWayRpcOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); - } - - @Test - public void testGatewayServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processProtoOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); - } - - @Test - public void testGatewayServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { - super.processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, true, true); - processProtoTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); - } - -} 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 00198666e2..33256bbf2f 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 @@ -54,9 +54,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -/** - * @author Valerii Sosliuk - */ @Slf4j public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractMqttIntegrationTest { @@ -76,12 +73,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM private static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; - protected Long asyncContextTimeoutToUseRpcPlugin; - - protected void processBeforeTest(String deviceName, String gatewayName, TransportPayloadType payloadType, String telemetryTopic, String attributesTopic) throws Exception { - super.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic); - asyncContextTimeoutToUseRpcPlugin = 10000L; - } + protected static final Long asyncContextTimeoutToUseRpcPlugin = 10000L; protected void processOneWayRpcTest(String rpcSubTopic) throws Exception { MqttAsyncClient client = getMqttAsyncClient(accessToken); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java new file mode 100644 index 0000000000..465063004e --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java @@ -0,0 +1,159 @@ +/** + * Copyright © 2016-2022 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.rpc; + +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Test; +import org.thingsboard.server.common.data.TransportPayloadType; +import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; + +@Slf4j +@DaoSqlTest +public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { + + @Test + public void testServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + processBeforeTest(configProperties); + processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); + } + + @Test + public void testServerMqttOneWayRpcOnShortTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); + } + + @Test + public void testServerMqttOneWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processOneWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); + } + + @Test + public void testServerMqttTwoWayRpcWithEnabledJsonCompatibility() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .build(); + super.processBeforeTest(configProperties); + processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); + } + + @Test + public void testServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC); + } + + @Test + public void testServerMqttTwoWayRpcOnShortTopic() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .build(); + super.processBeforeTest(configProperties); + processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_TOPIC); + } + + @Test + public void testServerMqttTwoWayRpcOnShortProtoTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processProtoTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_PROTO_TOPIC); + } + + @Test + public void testServerMqttTwoWayRpcOnShortJsonTopicWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processJsonTwoWayRpcTest(MqttTopics.DEVICE_RPC_REQUESTS_SUB_SHORT_JSON_TOPIC); + } + + @Test + public void testGatewayServerMqttOneWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .gatewayName("RPC test gateway") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processProtoOneWayRpcTestGateway("Gateway Device OneWay RPC Proto"); + } + + @Test + public void testGatewayServerMqttTwoWayRpcWithEnabledJsonCompatibilityAndJsonDownlinks() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .gatewayName("RPC test gateway") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .enableCompatibilityWithJsonPayloadFormat(true) + .useJsonPayloadFormatForDefaultDownlinkTopics(true) + .build(); + super.processBeforeTest(configProperties); + processProtoTwoWayRpcTestGateway("Gateway Device TwoWay RPC Proto"); + } + +} 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/MqttServerSideRpcDefaultIntegrationTest.java similarity index 89% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcDefaultIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcDefaultIntegrationTest.java index 973ffc7b17..542843eb2c 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/MqttServerSideRpcDefaultIntegrationTest.java @@ -17,29 +17,27 @@ package org.thingsboard.server.transport.mqtt.rpc; import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.security.AccessValidator; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -/** - * @author Valerii Sosliuk - */ @Slf4j -public abstract class AbstractMqttServerSideRpcDefaultIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { +@DaoSqlTest +public class MqttServerSideRpcDefaultIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", "RPC test gateway", null, null, null); - } - - @After - public void afterTest() throws Exception { - super.processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .gatewayName("RPC test gateway") + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcJsonIntegrationTest.java similarity index 83% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcJsonIntegrationTest.java index dd850b71ee..2fc95e5f6e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcJsonIntegrationTest.java @@ -17,23 +17,25 @@ package org.thingsboard.server.transport.mqtt.rpc; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; @Slf4j -public abstract class AbstractMqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { +@DaoSqlTest +public class MqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.JSON, null, null); - } - - @After - public void afterTest() throws Exception { - super.processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .gatewayName("RPC test gateway") + .transportPayloadType(TransportPayloadType.JSON) + .build(); + processBeforeTest(configProperties); } @Test 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/MqttServerSideRpcProtoIntegrationTest.java similarity index 78% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/MqttServerSideRpcProtoIntegrationTest.java index db70e99b2f..dcd06f5a52 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/MqttServerSideRpcProtoIntegrationTest.java @@ -16,24 +16,26 @@ package org.thingsboard.server.transport.mqtt.rpc; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; @Slf4j -public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { +@DaoSqlTest +public class MqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED, false, false); - } - - @After - public void afterTest() throws Exception { - super.processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("RPC test device") + .gatewayName("RPC test gateway") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .rpcRequestProtoSchema(RPC_REQUEST_PROTO_SCHEMA) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcIntegrationTest.java deleted file mode 100644 index a4fccf3941..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcIntegrationTest.java +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright © 2016-2022 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.rpc.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcDefaultIntegrationTest; - -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ -@DaoSqlTest -public class MqttServerSideRpcIntegrationTest extends AbstractMqttServerSideRpcDefaultIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonIntegrationTest.java deleted file mode 100644 index b3bbd8b493..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcJsonIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.rpc.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcJsonIntegrationTest; - -@DaoSqlTest -public class MqttServerSideRpcJsonIntegrationTest extends AbstractMqttServerSideRpcJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesIntegrationTest.java similarity index 95% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesIntegrationTest.java index f00964b6ac..ff97cf8716 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesIntegrationTest.java @@ -19,13 +19,14 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.Arrays; import java.util.HashSet; @@ -39,19 +40,19 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @Slf4j -public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqttIntegrationTest { +@DaoSqlTest +public class MqttAttributesIntegrationTest extends AbstractMqttIntegrationTest { protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; @Before public void beforeTest() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", null, null, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .gatewayName("Test Post Attributes gateway") + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesJsonIntegrationTest.java similarity index 73% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesJsonIntegrationTest.java index 44f3aeb9b3..f77885c0fd 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesJsonIntegrationTest.java @@ -16,28 +16,30 @@ package org.thingsboard.server.transport.mqtt.telemetry.attributes; import lombok.extern.slf4j.Slf4j; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.Arrays; import java.util.List; @Slf4j -public abstract class AbstractMqttAttributesJsonIntegrationTest extends AbstractMqttAttributesIntegrationTest { +@DaoSqlTest +public class MqttAttributesJsonIntegrationTest extends MqttAttributesIntegrationTest { private static final String POST_DATA_ATTRIBUTES_TOPIC = "data/attributes"; @Before public void beforeTest() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.JSON, null, POST_DATA_ATTRIBUTES_TOPIC); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .gatewayName("Test Post Attributes gateway") + .transportPayloadType(TransportPayloadType.JSON) + .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) + .build(); + processBeforeTest(configProperties); } @Test diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java similarity index 79% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java index ef3ff3c8a1..e6295a1a3d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/AbstractMqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java @@ -28,8 +28,10 @@ import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransp import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.Arrays; import java.util.List; @@ -38,7 +40,8 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @Slf4j -public abstract class AbstractMqttAttributesProtoIntegrationTest extends AbstractMqttAttributesIntegrationTest { +@DaoSqlTest +public class MqttAttributesProtoIntegrationTest extends MqttAttributesIntegrationTest { private static final String POST_DATA_ATTRIBUTES_TOPIC = "proto/attributes"; @@ -50,20 +53,36 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac @Test public void testPushAttributes() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicMessage postAttributesMsg = getDefaultDynamicMessage(); processAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false); } @Test public void testPushAttributesWithEnabledJsonBackwardCompatibility() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC, true, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) + .enableCompatibilityWithJsonPayloadFormat(true) + .build(); + processBeforeTest(configProperties); processJsonPayloadAttributesTest(POST_DATA_ATTRIBUTES_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes()); } @Test public void testPushAttributesWithExplicitPresenceProtoKeys() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicSchema attributesSchema = getDynamicSchema(); DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); @@ -96,27 +115,47 @@ public abstract class AbstractMqttAttributesProtoIntegrationTest extends Abstrac @Test public void testPushAttributesOnShortTopic() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicMessage postAttributesMsg = getDefaultDynamicMessage(); processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false); } @Test public void testPushAttributesOnShortJsonTopic() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) + .build(); + processBeforeTest(configProperties); processJsonPayloadAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes()); } @Test public void testPushAttributesOnShortProtoTopic() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, POST_DATA_ATTRIBUTES_TOPIC); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .attributesTopicFilter(POST_DATA_ATTRIBUTES_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicMessage postAttributesMsg = getDefaultDynamicMessage(); processAttributesTest(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postAttributesMsg.toByteArray(), false); } @Test public void testPushAttributesGateway() throws Exception { - processBeforeTest("Test Post Attributes device", "Test Post Attributes gateway", TransportPayloadType.PROTOBUF, null, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Attributes device") + .gatewayName("Test Post Attributes gateway") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .build(); + processBeforeTest(configProperties); TransportApiProtos.GatewayAttributesMsg.Builder gatewayAttributesMsgProtoBuilder = TransportApiProtos.GatewayAttributesMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); String deviceName1 = "Device A"; diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesIntegrationTest.java deleted file mode 100644 index 2366dd6d3d..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.telemetry.attributes.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesIntegrationTest; - -@DaoSqlTest -public class MqttAttributesIntegrationTest extends AbstractMqttAttributesIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesJsonIntegrationTest.java deleted file mode 100644 index 0737a74610..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesJsonIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.telemetry.attributes.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesJsonIntegrationTest; - -@DaoSqlTest -public class MqttAttributesJsonIntegrationTest extends AbstractMqttAttributesJsonIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesProtoIntegrationTest.java deleted file mode 100644 index 5ce6c34909..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/sql/MqttAttributesProtoIntegrationTest.java +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright © 2016-2022 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.telemetry.attributes.sql; - -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.telemetry.attributes.AbstractMqttAttributesProtoIntegrationTest; - -@DaoSqlTest -public class MqttAttributesProtoIntegrationTest extends AbstractMqttAttributesProtoIntegrationTest { -} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java index 57f3ee9701..8b1242e592 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesIntegrationTest.java @@ -23,14 +23,15 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttCallback; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.eclipse.paho.client.mqttv3.internal.wire.MqttWireMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.transport.mqtt.AbstractMqttIntegrationTest; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.Arrays; import java.util.HashSet; @@ -50,14 +51,16 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt protected static final String PAYLOAD_VALUES_STR = "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4," + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; + protected static final String MALFORMED_JSON_PAYLOAD = "{\"key1\":, \"key2\":true, \"key3\": 3.0, \"key4\": 4," + + " \"key5\": {\"someNumber\": 42, \"someArray\": [1,2,3], \"someNestedObject\": {\"key\": \"value\"}}}"; + @Before public void beforeTest() throws Exception { - processBeforeTest("Test Post Telemetry device", "Test Post Telemetry gateway", null, null, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device") + .gatewayName("Test Post Telemetry gateway") + .build(); + processBeforeTest(configProperties); } @Test @@ -368,5 +371,34 @@ public abstract class AbstractMqttTimeseriesIntegrationTest extends AbstractMqtt } } + public static class TestMqttPublishCallback implements MqttCallback { + + private final CountDownLatch latch; + private boolean pubAckReceived; + + public boolean isPubAckReceived() { + return pubAckReceived; + } + + public TestMqttPublishCallback(CountDownLatch latch) { + this.latch = latch; + } + + @Override + public void connectionLost(Throwable throwable) { + latch.countDown(); + } + + @Override + public void messageArrived(String s, MqttMessage mqttMessage) throws Exception { + } + + @Override + public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { + pubAckReceived = iMqttDeliveryToken.getResponse().getType() == MqttWireMessage.MESSAGE_TYPE_PUBACK; + latch.countDown(); + } + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java index 85fb96bfec..920a5692a5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesJsonIntegrationTest.java @@ -17,17 +17,18 @@ package org.thingsboard.server.transport.mqtt.telemetry.timeseries; import lombok.extern.slf4j.Slf4j; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; @Slf4j public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { @@ -35,23 +36,31 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract private static final String POST_DATA_TELEMETRY_TOPIC = "data/telemetry"; @Before + @Override public void beforeTest() throws Exception { - processBeforeTest("Test Post Telemetry device json payload", "Test Post Telemetry gateway json payload", TransportPayloadType.JSON, POST_DATA_TELEMETRY_TOPIC, null); - } - - @After - public void afterTest() throws Exception { - processAfterTest(); + //do nothing, processBeforeTest will be invoked in particular test methods with different parameters } @Test public void testPushTelemetry() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, PAYLOAD_VALUES_STR.getBytes(), false); } @Test public void testPushTelemetryWithTs() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); String payloadStr = "{\"ts\": 10000, \"values\": " + PAYLOAD_VALUES_STR + "}"; List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, expectedKeys, payloadStr.getBytes(), true); @@ -59,21 +68,120 @@ public abstract class AbstractMqttTimeseriesJsonIntegrationTest extends Abstract @Test public void testPushTelemetryOnShortTopic() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); super.testPushTelemetryOnShortTopic(); } @Test - public void testPushTelemetryWithTsOnShortJsonTopic() throws Exception { + public void testPushTelemetryOnShortJsonTopic() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); super.testPushTelemetryOnShortJsonTopic(); } @Test public void testPushTelemetryGateway() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .gatewayName("Test Post Telemetry gateway json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); super.testPushTelemetryGateway(); } @Test public void testGatewayConnect() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .gatewayName("Test Post Telemetry gateway json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); super.testGatewayConnect(); } + + @Test + public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .sendAckOnValidationException(true) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(accessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertTrue(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(accessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertFalse(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .gatewayName("Test Post Telemetry gateway json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .sendAckOnValidationException(true) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertTrue(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device json payload") + .gatewayName("Test Post Telemetry gateway json payload") + .transportPayloadType(TransportPayloadType.JSON) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertFalse(callback.isPubAckReceived()); + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index ecb2233332..f9bf5471ef 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -24,7 +24,6 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; @@ -33,10 +32,14 @@ import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadCo import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.MqttTestConfigProperties; import java.util.Arrays; import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -44,6 +47,7 @@ import static org.junit.Assert.assertTrue; public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { private static final String POST_DATA_TELEMETRY_TOPIC = "proto/telemetry"; + private static final String MALFORMED_PROTO_PAYLOAD = "invalid proto payload str"; @Before @Override @@ -53,14 +57,25 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test public void testPushTelemetry() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicMessage postTelemetryMsg = getDefaultDynamicMessage(); processTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false); } @Test public void testPushTelemetryWithEnabledJsonBackwardCompatibility() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, true, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .enableCompatibilityWithJsonPayloadFormat(true) + .build(); + processBeforeTest(configProperties); processJsonPayloadTelemetryTest(POST_DATA_TELEMETRY_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes(), false); } @@ -91,7 +106,13 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac " }\n" + " }\n" + "}"; - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .telemetryProtoSchema(schemaStr) + .build(); + processBeforeTest(configProperties); DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); @@ -136,7 +157,12 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test public void testPushTelemetryWithExplicitPresenceProtoKeys() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicSchema telemetrySchema = getDynamicSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); @@ -193,7 +219,13 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac " }\n" + " }\n" + "}"; - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .telemetryProtoSchema(schemaStr) + .build(); + processBeforeTest(configProperties); DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); @@ -233,27 +265,48 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test public void testPushTelemetryOnShortTopic() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicMessage postTelemetryMsg = getDefaultDynamicMessage(); processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false); } @Test public void testPushTelemetryOnShortJsonTopic() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); processJsonPayloadTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), PAYLOAD_VALUES_STR.getBytes(), false); } @Test public void testPushTelemetryOnShortProtoTopic() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); DynamicMessage postTelemetryMsg = getDefaultDynamicMessage(); processTelemetryTest(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC, Arrays.asList("key1", "key2", "key3", "key4", "key5"), postTelemetryMsg.toByteArray(), false, false); } @Test public void testPushTelemetryGateway() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .gatewayName("Test Post Telemetry gateway proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); String deviceName1 = "Device A"; @@ -267,7 +320,13 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test public void testGatewayConnect() throws Exception { - processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED, false, false); + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .gatewayName("Test Post Telemetry gateway proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); String deviceName = "Device A"; TransportApiProtos.ConnectMsg connectMsgProto = getConnectProto(deviceName); MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); @@ -280,6 +339,116 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac assertNotNull(device); } + + @Test + public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .sendAckOnValidationException(true) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(accessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertTrue(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(accessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertFalse(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorEnabledAndBackwardCompatibilityEnabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .enableCompatibilityWithJsonPayloadFormat(true) + .sendAckOnValidationException(true) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(accessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertTrue(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryWithMalformedPayloadAndSendAckOnErrorDisabledAndBackwardCompatibilityEnabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .enableCompatibilityWithJsonPayloadFormat(true) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(accessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_JSON_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertFalse(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorEnabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .gatewayName("Test Post Telemetry gateway proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .sendAckOnValidationException(true) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertTrue(callback.isPubAckReceived()); + } + + @Test + public void testPushTelemetryGatewayWithMalformedPayloadAndSendAckOnErrorDisabled() throws Exception { + MqttTestConfigProperties configProperties = MqttTestConfigProperties.builder() + .deviceName("Test Post Telemetry device proto payload") + .gatewayName("Test Post Telemetry gateway proto payload") + .transportPayloadType(TransportPayloadType.PROTOBUF) + .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) + .build(); + processBeforeTest(configProperties); + CountDownLatch latch = new CountDownLatch(1); + MqttAsyncClient client = getMqttAsyncClient(gatewayAccessToken); + TestMqttPublishCallback callback = new TestMqttPublishCallback(latch); + client.setCallback(callback); + publishMqttMsg(client, MALFORMED_PROTO_PAYLOAD.getBytes(), POST_DATA_TELEMETRY_TOPIC); + latch.await(3, TimeUnit.SECONDS); + assertFalse(callback.isPubAckReceived()); + } + private DynamicSchema getDynamicSchema(String deviceTelemetryProtoSchema) { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java index 8712238224..7006638eb1 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/nosql/MqttTimeseriesNoSqlIntegrationTest.java @@ -18,9 +18,6 @@ package org.thingsboard.server.transport.mqtt.telemetry.timeseries.nosql; import org.thingsboard.server.dao.service.DaoNoSqlTest; import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ @DaoNoSqlTest public class MqttTimeseriesNoSqlIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java index a14e81f52e..de9a596d43 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlIntegrationTest.java @@ -18,9 +18,6 @@ package org.thingsboard.server.transport.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesIntegrationTest; -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ @DaoSqlTest public class MqttTimeseriesSqlIntegrationTest extends AbstractMqttTimeseriesIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java index f60fb6679e..323dd751f1 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlJsonIntegrationTest.java @@ -18,9 +18,6 @@ package org.thingsboard.server.transport.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesJsonIntegrationTest; -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ @DaoSqlTest public class MqttTimeseriesSqlJsonIntegrationTest extends AbstractMqttTimeseriesJsonIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java index 383452de83..ebfc1d49cf 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/sql/MqttTimeseriesSqlProtoIntegrationTest.java @@ -18,9 +18,6 @@ package org.thingsboard.server.transport.mqtt.telemetry.timeseries.sql; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.mqtt.telemetry.timeseries.AbstractMqttTimeseriesProtoIntegrationTest; -/** - * Created by Valerii Sosliuk on 8/22/2017. - */ @DaoSqlTest public class MqttTimeseriesSqlProtoIntegrationTest extends AbstractMqttTimeseriesProtoIntegrationTest { } diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index d919319096..518c9b42d3 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -8,7 +8,50 @@ transport.lwm2m.security.trust-credentials.enabled=true transport.lwm2m.security.trust-credentials.type=KEYSTORE transport.lwm2m.security.trust-credentials.keystore.store_file=lwm2m/credentials/lwm2mtruststorechain.jks -edges.enabled=true +# Edge disabled to speed up the context init. Will be enabled by @TestPropertySource in respective tests +edges.enabled=false edges.storage.no_read_records_sleep=500 edges.storage.sleep_between_batches=500 actors.rpc.sequential=true + +# Transports disabled to speed up the context init. Particular transport will be enabled with @TestPropertySource in respective tests +transport.http.enabled=false +transport.mqtt.enabled=false +transport.coap.enabled=false +transport.lwm2m.enabled=false +transport.snmp.enabled=false + +# Low latency settings to perform tests as fast as possible +sql.attributes.batch_max_delay=5 +sql.attributes.batch_threads=2 +sql.ts.batch_max_delay=5 +sql.ts.batch_threads=2 +sql.ts_latest.batch_max_delay=5 +sql.ts_latest.batch_threads=2 +sql.events.batch_max_delay=5 +sql.events.batch_threads=2 +actors.system.tenant_dispatcher_pool_size=4 +actors.system.device_dispatcher_pool_size=8 +actors.system.rule_dispatcher_pool_size=12 +transport.sessions.report_timeout=10000 +queue.transport_api.request_poll_interval=5 +queue.transport_api.response_poll_interval=5 +queue.transport.poll_interval=5 +queue.core.poll-interval=5 +queue.core.partitions=2 +queue.rule-engine.poll-interval=5 +queue.rule-engine.queues[0].poll-interval=5 +queue.rule-engine.queues[0].partitions=2 +queue.rule-engine.queues[0].processing-strategy.retries=1 +queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 +queue.rule-engine.queues[1].poll-interval=5 +queue.rule-engine.queues[1].partitions=2 +queue.rule-engine.queues[1].processing-strategy.retries=1 +queue.rule-engine.queues[1].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[1].processing-strategy.max-pause-between-retries=0 +queue.rule-engine.queues[2].poll-interval=5 +queue.rule-engine.queues[2].partitions=2 +queue.rule-engine.queues[2].processing-strategy.retries=1 +queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0 diff --git a/application/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/application/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..ca6ee9cea8 --- /dev/null +++ b/application/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 9692a76b91..a06fa00b55 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index 351a23ea4c..47834cb6bb 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java index ff1452f5f0..ebf031c562 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java @@ -33,6 +33,8 @@ import org.springframework.util.Assert; import org.thingsboard.server.common.data.id.EntityId; import redis.clients.jedis.JedisPoolConfig; +import java.time.Duration; + @Configuration @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false) @EnableCaching @@ -114,8 +116,8 @@ public abstract class TBRedisCacheConfiguration { poolConfig.setTestOnBorrow(testOnBorrow); poolConfig.setTestOnReturn(testOnReturn); poolConfig.setTestWhileIdle(testWhileIdle); - poolConfig.setMinEvictableIdleTimeMillis(minEvictableMs); - poolConfig.setTimeBetweenEvictionRunsMillis(evictionRunsMs); + poolConfig.setSoftMinEvictableIdleTime(Duration.ofMillis(minEvictableMs)); + poolConfig.setTimeBetweenEvictionRuns(Duration.ofMillis(evictionRunsMs)); poolConfig.setMaxWaitMillis(maxWaitMills); poolConfig.setNumTestsPerEvictionRun(numberTestsPerEvictionRun); poolConfig.setBlockWhenExhausted(blockWhenExhausted); diff --git a/common/cluster-api/pom.xml b/common/cluster-api/pom.xml index 95f27310ce..b531f26597 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index a19101b51d..fad7c87420 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index 77843ea3e2..931d7dc6d4 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common 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 e8ec430bcf..e9950447db 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,6 +15,7 @@ */ 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; @@ -23,7 +24,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; public interface EdgeEventService { - EdgeEvent save(EdgeEvent edgeEvent); + ListenableFuture saveAsync(EdgeEvent edgeEvent); PageData findEdgeEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index 51b4468d01..ea95923275 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -42,7 +42,7 @@ public interface EdgeService { Optional findEdgeByRoutingKey(TenantId tenantId, String routingKey); - Edge saveEdge(Edge edge, boolean doValidate); + Edge saveEdge(Edge edge); Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index 7deb9cf2e6..83624df8e3 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -28,11 +28,7 @@ import java.util.Optional; public interface EventService { - Event save(Event event); - - ListenableFuture saveAsync(Event event); - - Optional saveIfNotExists(Event event); + ListenableFuture saveAsync(Event event); Optional findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid); @@ -48,6 +44,6 @@ public interface EventService { void removeEvents(TenantId tenantId, EntityId entityId, EventFilter eventFilter, Long startTime, Long endTime); - void cleanupEvents(long ttl, long debugTtl); + void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/TbAutoConfiguration.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/TbAutoConfiguration.java new file mode 100644 index 0000000000..ee870a4aea --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/TbAutoConfiguration.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 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.dao.util; + +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration; +import org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration; +import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@EnableAutoConfiguration(exclude = {CassandraAutoConfiguration.class, CassandraDataAutoConfiguration.class, RedisAutoConfiguration.class}) +public @interface TbAutoConfiguration { +} diff --git a/common/data/pom.xml b/common/data/pom.xml index 450a1cd33d..6c145553c0 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java index b5ac0b53e5..d62efd6fcb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java @@ -15,8 +15,14 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; +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 java.util.concurrent.ThreadLocalRandom; @@ -63,4 +69,22 @@ public final class EdgeUtils { public static int nextPositiveInt() { return ThreadLocalRandom.current().nextInt(0, Integer.MAX_VALUE); } + + public static EdgeEvent constructEdgeEvent(TenantId tenantId, + EdgeId edgeId, + EdgeEventType type, + EdgeEventActionType action, + EntityId entityId, + JsonNode body) { + EdgeEvent edgeEvent = new EdgeEvent(); + edgeEvent.setTenantId(tenantId); + edgeEvent.setEdgeId(edgeId); + edgeEvent.setType(type); + edgeEvent.setAction(action); + if (entityId != null) { + edgeEvent.setEntityId(entityId.getId()); + } + edgeEvent.setBody(body); + return edgeEvent; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java index 2283903472..1a4d2c72ad 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttDeviceProfileTransportConfiguration.java @@ -27,6 +27,7 @@ public class MqttDeviceProfileTransportConfiguration implements DeviceProfileTra @NoXss private String deviceAttributesTopic = MqttTopics.DEVICE_ATTRIBUTES_TOPIC; private TransportPayloadTypeConfiguration transportPayloadTypeConfiguration; + private boolean sendAckOnValidationException; @Override public DeviceTransportType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java index 0881b01c93..73caea088d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java @@ -16,11 +16,15 @@ package org.thingsboard.server.common.data.device.profile.lwm2m; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; @Data +@NoArgsConstructor +@AllArgsConstructor @JsonIgnoreProperties(ignoreUnknown = true) public class OtherConfiguration extends PowerSavingConfiguration { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java index 7d0f32d298..cb6f4e7695 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java @@ -15,13 +15,16 @@ */ package org.thingsboard.server.common.data.device.profile.lwm2m; +import lombok.AllArgsConstructor; import lombok.Data; -import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; +import lombok.NoArgsConstructor; import java.util.Map; import java.util.Set; @Data +@NoArgsConstructor +@AllArgsConstructor public class TelemetryMappingConfiguration { private Map keyName; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/DeviceTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/DeviceTypeFilter.java index 5c5d5d83df..d616a68783 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/DeviceTypeFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/DeviceTypeFilter.java @@ -15,8 +15,12 @@ */ package org.thingsboard.server.common.data.query; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; +@NoArgsConstructor +@AllArgsConstructor @Data public class DeviceTypeFilter implements EntityFilter { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java index d51725f153..7bd19e2441 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java @@ -59,15 +59,4 @@ public class RuleChainMetaData { } connections.add(connectionInfo); } - public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type, JsonNode additionalInfo) { - RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo(); - connectionInfo.setFromIndex(fromIndex); - connectionInfo.setTargetRuleChainId(targetRuleChainId); - connectionInfo.setType(type); - connectionInfo.setAdditionalInfo(additionalInfo); - if (ruleChainConnections == null) { - ruleChainConnections = new ArrayList<>(); - } - ruleChainConnections.add(connectionInfo); - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmppSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmppSmsProviderConfiguration.java new file mode 100644 index 0000000000..1c94097500 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmppSmsProviderConfiguration.java @@ -0,0 +1,117 @@ +/** + * Copyright © 2016-2022 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.common.data.sms.config; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +public class SmppSmsProviderConfiguration implements SmsProviderConfiguration { + @ApiModelProperty(value = "SMPP version", allowableValues = "3.3, 3.4", required = true) + private String protocolVersion; + + @ApiModelProperty(value = "SMPP host", required = true) + private String host; + @ApiModelProperty(value = "SMPP port", required = true) + private Integer port; + + @ApiModelProperty(value = "System ID", required = true) + private String systemId; + @ApiModelProperty(value = "Password", required = true) + private String password; + + @ApiModelProperty(value = "System type", required = false) + private String systemType; + @ApiModelProperty(value = "TX - Transmitter, RX - Receiver, TRX - Transciever. By default TX is used", required = false) + private SmppBindType bindType; + @ApiModelProperty(value = "Service type", required = false) + private String serviceType; + + @ApiModelProperty(value = "Source address", required = false) + private String sourceAddress; + @ApiModelProperty(value = "Source TON (Type of Number). Needed is source address is set. 5 by default.\n" + + "0 - Unknown\n" + + "1 - International\n" + + "2 - National\n" + + "3 - Network Specific\n" + + "4 - Subscriber Number\n" + + "5 - Alphanumeric\n" + + "6 - Abbreviated", required = false) + private Byte sourceTon; + @ApiModelProperty(value = "Source NPI (Numbering Plan Identification). Needed is source address is set. 0 by default.\n" + + "0 - Unknown\n" + + "1 - ISDN/telephone numbering plan (E163/E164)\n" + + "3 - Data numbering plan (X.121)\n" + + "4 - Telex numbering plan (F.69)\n" + + "6 - Land Mobile (E.212) =6\n" + + "8 - National numbering plan\n" + + "9 - Private numbering plan\n" + + "10 - ERMES numbering plan (ETSI DE/PS 3 01-3)\n" + + "13 - Internet (IP)\n" + + "18 - WAP Client Id (to be defined by WAP Forum)", required = false) + private Byte sourceNpi; + + @ApiModelProperty(value = "Destination TON (Type of Number). 5 by default.\n" + + "0 - Unknown\n" + + "1 - International\n" + + "2 - National\n" + + "3 - Network Specific\n" + + "4 - Subscriber Number\n" + + "5 - Alphanumeric\n" + + "6 - Abbreviated", required = false) + private Byte destinationTon; + @ApiModelProperty(value = "Destination NPI (Numbering Plan Identification). 0 by default.\n" + + "0 - Unknown\n" + + "1 - ISDN/telephone numbering plan (E163/E164)\n" + + "3 - Data numbering plan (X.121)\n" + + "4 - Telex numbering plan (F.69)\n" + + "6 - Land Mobile (E.212) =6\n" + + "8 - National numbering plan\n" + + "9 - Private numbering plan\n" + + "10 - ERMES numbering plan (ETSI DE/PS 3 01-3)\n" + + "13 - Internet (IP)\n" + + "18 - WAP Client Id (to be defined by WAP Forum)", required = false) + private Byte destinationNpi; + + @ApiModelProperty(value = "Address range", required = false) + private String addressRange; + + @ApiModelProperty(allowableValues = "0-10,13-14", + value = "0 - SMSC Default Alphabet (ASCII for short and long code and to GSM for toll-free, used as default)\n" + + "1 - IA5 (ASCII for short and long code, Latin 9 for toll-free (ISO-8859-9))\n" + + "2 - Octet Unspecified (8-bit binary)\n" + + "3 - Latin 1 (ISO-8859-1)\n" + + "4 - Octet Unspecified (8-bit binary)\n" + + "5 - JIS (X 0208-1990)\n" + + "6 - Cyrillic (ISO-8859-5)\n" + + "7 - Latin/Hebrew (ISO-8859-8)\n" + + "8 - UCS2/UTF-16 (ISO/IEC-10646)\n" + + "9 - Pictogram Encoding\n" + + "10 - Music Codes (ISO-2022-JP)\n" + + "13 - Extended Kanji JIS (X 0212-1990)\n" + + "14 - Korean Graphic Character Set (KS C 5601/KS X 1001)", required = false) + private Byte codingScheme; + + @Override + public SmsProviderType getType() { + return SmsProviderType.SMPP; + } + + public enum SmppBindType { + TX, RX, TRX + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java index 4feaf6e07c..cf5892dd8b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderConfiguration.java @@ -27,7 +27,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; property = "type") @JsonSubTypes({ @JsonSubTypes.Type(value = AwsSnsSmsProviderConfiguration.class, name = "AWS_SNS"), - @JsonSubTypes.Type(value = TwilioSmsProviderConfiguration.class, name = "TWILIO")}) + @JsonSubTypes.Type(value = TwilioSmsProviderConfiguration.class, name = "TWILIO"), + @JsonSubTypes.Type(value = SmppSmsProviderConfiguration.class, name = "SMPP") +}) public interface SmsProviderConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java index 4bdb831862..9abdc1dd2e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/SmsProviderType.java @@ -17,5 +17,6 @@ package org.thingsboard.server.common.data.sms.config; public enum SmsProviderType { AWS_SNS, - TWILIO + TWILIO, + SMPP } diff --git a/common/edge-api/pom.xml b/common/edge-api/pom.xml index 78eb539049..0381a19de9 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/message/pom.xml b/common/message/pom.xml index 0ec15a459c..3948f8e7b8 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java index 6ac45ae67b..11c9a984f2 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java @@ -38,7 +38,8 @@ public final class TbMsgMetaData implements Serializable { } public TbMsgMetaData(Map data) { - this.data = new ConcurrentHashMap<>(data); + this.data = new ConcurrentHashMap<>(); + data.forEach(this::putValue); } /** @@ -49,20 +50,20 @@ public final class TbMsgMetaData implements Serializable { } public String getValue(String key) { - return data.get(key); + return this.data.get(key); } public void putValue(String key, String value) { if (key != null && value != null) { - data.put(key, value); + this.data.put(key, value); } } public Map values() { - return new HashMap<>(data); + return new HashMap<>(this.data); } public TbMsgMetaData copy() { - return new TbMsgMetaData(data); + return new TbMsgMetaData(this.data); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java index 2552f8de4a..d6761d1ba7 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java @@ -17,7 +17,9 @@ package org.thingsboard.server.common.msg; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; +@ToString @EqualsAndHashCode public abstract class TbRuleEngineActorMsg implements TbActorMsg { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java index 7c28a1ad9e..b5c43f20a3 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java @@ -29,7 +29,7 @@ import java.util.Set; /** * Created by ashvayka on 15.03.18. */ -@ToString +@ToString(callSuper = true) @EqualsAndHashCode(callSuper = true) public final class QueueToRuleEngineMsg extends TbRuleEngineActorMsg { diff --git a/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgMetaDataTest.java b/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgMetaDataTest.java new file mode 100644 index 0000000000..9db1285350 --- /dev/null +++ b/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgMetaDataTest.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2022 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.common.msg; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Before; +import org.junit.Test; + +import java.util.Map; + +import static org.junit.Assert.assertEquals; + +public class TbMsgMetaDataTest { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + private final String metadataJsonStr = "{\"deviceName\":\"Test Device\",\"deviceType\":\"default\",\"ts\":\"1645112691407\"}"; + private JsonNode metadataJson; + private Map metadataExpected; + + @Before + public void startInit() throws Exception { + metadataJson = objectMapper.readValue(metadataJsonStr, JsonNode.class); + metadataExpected = objectMapper.convertValue(metadataJson, new TypeReference<>() { + }); + } + + @Test + public void testScript_whenMetadataWithoutPropertiesValueNull_returnMetadataWithAllValue() { + TbMsgMetaData tbMsgMetaData = new TbMsgMetaData(metadataExpected); + Map dataActual = tbMsgMetaData.values(); + assertEquals(metadataExpected.size(), dataActual.size()); + } + + @Test + public void testScript_whenMetadataWithPropertiesValueNull_returnMetadataWithoutPropertiesValueEqualsNull() { + metadataExpected.put("deviceName", null); + TbMsgMetaData tbMsgMetaData = new TbMsgMetaData(metadataExpected); + Map dataActual = tbMsgMetaData.copy().getData(); + assertEquals(metadataExpected.size() - 1, dataActual.size()); + } +} diff --git a/common/pom.xml b/common/pom.xml index e3a246a13c..236fb50a19 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 8240a4070f..e2197d566f 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/memory/DefaultInMemoryStorage.java b/common/queue/src/main/java/org/thingsboard/server/queue/memory/DefaultInMemoryStorage.java new file mode 100644 index 0000000000..6408729877 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/memory/DefaultInMemoryStorage.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016-2022 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.queue.memory; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.TbQueueMsg; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.LinkedBlockingQueue; + +@Component +@Slf4j +public final class DefaultInMemoryStorage implements InMemoryStorage { + private final ConcurrentHashMap> storage = new ConcurrentHashMap<>(); + + @Override + public void printStats() { + if (log.isDebugEnabled()) { + storage.forEach((topic, queue) -> { + if (queue.size() > 0) { + log.debug("[{}] Queue Size [{}]", topic, queue.size()); + } + }); + } + } + + @Override + public int getLagTotal() { + return storage.values().stream().map(BlockingQueue::size).reduce(0, Integer::sum); + } + + @Override + public boolean put(String topic, TbQueueMsg msg) { + return storage.computeIfAbsent(topic, (t) -> new LinkedBlockingQueue<>()).add(msg); + } + + @SuppressWarnings("unchecked") + @Override + public List get(String topic) throws InterruptedException { + final BlockingQueue queue = storage.get(topic); + if (queue != null) { + final TbQueueMsg firstMsg = queue.poll(); + if (firstMsg != null) { + final int queueSize = queue.size(); + if (queueSize > 0) { + final List entities = new ArrayList<>(Math.min(queueSize, 999) + 1); + entities.add(firstMsg); + queue.drainTo(entities, 999); + return (List) entities; + } + return Collections.singletonList((T) firstMsg); + } + } + return Collections.emptyList(); + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java index 8bf72d4de6..0fdfebbfff 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryStorage.java @@ -15,80 +15,18 @@ */ package org.thingsboard.server.queue.memory; -import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.queue.TbQueueMsg; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.LinkedBlockingQueue; -@Slf4j -public final class InMemoryStorage { - private static InMemoryStorage instance; - private final ConcurrentHashMap> storage; +public interface InMemoryStorage { - private InMemoryStorage() { - storage = new ConcurrentHashMap<>(); - } + void printStats(); - public void printStats() { - storage.forEach((topic, queue) -> { - if (queue.size() > 0) { - log.debug("[{}] Queue Size [{}]", topic, queue.size()); - } - }); - } + int getLagTotal(); - public int getLagTotal() { - return storage.values().stream().map(BlockingQueue::size).reduce(0, Integer::sum); - } + boolean put(String topic, TbQueueMsg msg); - public static InMemoryStorage getInstance() { - if (instance == null) { - synchronized (InMemoryStorage.class) { - if (instance == null) { - instance = new InMemoryStorage(); - } - } - } - return instance; - } - - public boolean put(String topic, TbQueueMsg msg) { - return storage.computeIfAbsent(topic, (t) -> new LinkedBlockingQueue<>()).add(msg); - } - - public List get(String topic) throws InterruptedException { - if (storage.containsKey(topic)) { - List entities; - @SuppressWarnings("unchecked") - T first = (T) storage.get(topic).poll(); - if (first != null) { - entities = new ArrayList<>(); - entities.add(first); - List otherList = new ArrayList<>(); - storage.get(topic).drainTo(otherList, 999); - for (TbQueueMsg other : otherList) { - @SuppressWarnings("unchecked") - T entity = (T) other; - entities.add(entity); - } - } else { - entities = Collections.emptyList(); - } - return entities; - } - return Collections.emptyList(); - } - - /** - * Used primarily for testing. - */ - public void cleanup() { - storage.clear(); - } + List get(String topic) throws InterruptedException; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java index de96729e0b..76ce8f6572 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueConsumer.java @@ -27,12 +27,13 @@ import java.util.stream.Collectors; @Slf4j public class InMemoryTbQueueConsumer implements TbQueueConsumer { - private final InMemoryStorage storage = InMemoryStorage.getInstance(); + private final InMemoryStorage storage; private volatile Set partitions; private volatile boolean stopped; private volatile boolean subscribed; - public InMemoryTbQueueConsumer(String topic) { + public InMemoryTbQueueConsumer(InMemoryStorage storage, String topic) { + this.storage = storage; this.topic = topic; stopped = false; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueProducer.java b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueProducer.java index 5d3562c00d..ca305ed43d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueProducer.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/memory/InMemoryTbQueueProducer.java @@ -24,11 +24,12 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @Data public class InMemoryTbQueueProducer implements TbQueueProducer { - private final InMemoryStorage storage = InMemoryStorage.getInstance(); + private final InMemoryStorage storage; private final String defaultTopic; - public InMemoryTbQueueProducer(String defaultTopic) { + public InMemoryTbQueueProducer(InMemoryStorage storage, String defaultTopic) { + this.storage = storage; this.defaultTopic = defaultTopic; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java index 53de7c1397..fe87fc2886 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java @@ -55,69 +55,70 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE TbQueueRuleEngineSettings ruleEngineSettings, TbServiceInfoProvider serviceInfoProvider, TbQueueTransportApiSettings transportApiSettings, - TbQueueTransportNotificationSettings transportNotificationSettings) { + TbQueueTransportNotificationSettings transportNotificationSettings, + InMemoryStorage storage) { this.notificationsTopicService = notificationsTopicService; this.coreSettings = coreSettings; this.serviceInfoProvider = serviceInfoProvider; this.ruleEngineSettings = ruleEngineSettings; this.transportApiSettings = transportApiSettings; this.transportNotificationSettings = transportNotificationSettings; - this.storage = InMemoryStorage.getInstance(); + this.storage = storage; } @Override public TbQueueProducer> createTransportNotificationsMsgProducer() { - return new InMemoryTbQueueProducer<>(transportNotificationSettings.getNotificationsTopic()); + return new InMemoryTbQueueProducer<>(storage, transportNotificationSettings.getNotificationsTopic()); } @Override public TbQueueProducer> createRuleEngineMsgProducer() { - return new InMemoryTbQueueProducer<>(ruleEngineSettings.getTopic()); + return new InMemoryTbQueueProducer<>(storage, ruleEngineSettings.getTopic()); } @Override public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { - return new InMemoryTbQueueProducer<>(ruleEngineSettings.getTopic()); + return new InMemoryTbQueueProducer<>(storage, ruleEngineSettings.getTopic()); } @Override public TbQueueProducer> createTbCoreMsgProducer() { - return new InMemoryTbQueueProducer<>(coreSettings.getTopic()); + return new InMemoryTbQueueProducer<>(storage, coreSettings.getTopic()); } @Override public TbQueueProducer> createTbCoreNotificationsMsgProducer() { - return new InMemoryTbQueueProducer<>(coreSettings.getTopic()); + return new InMemoryTbQueueProducer<>(storage, coreSettings.getTopic()); } @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { - return new InMemoryTbQueueConsumer<>(configuration.getTopic()); + return new InMemoryTbQueueConsumer<>(storage, configuration.getTopic()); } @Override public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { - return new InMemoryTbQueueConsumer<>(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName()); + return new InMemoryTbQueueConsumer<>(storage, notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName()); } @Override public TbQueueConsumer> createToCoreMsgConsumer() { - return new InMemoryTbQueueConsumer<>(coreSettings.getTopic()); + return new InMemoryTbQueueConsumer<>(storage, coreSettings.getTopic()); } @Override public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { - return new InMemoryTbQueueConsumer<>(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName()); + return new InMemoryTbQueueConsumer<>(storage, notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName()); } @Override public TbQueueConsumer> createTransportApiRequestConsumer() { - return new InMemoryTbQueueConsumer<>(transportApiSettings.getRequestsTopic()); + return new InMemoryTbQueueConsumer<>(storage, transportApiSettings.getRequestsTopic()); } @Override public TbQueueProducer> createTransportApiResponseProducer() { - return new InMemoryTbQueueProducer<>(transportApiSettings.getResponsesTopic()); + return new InMemoryTbQueueProducer<>(storage, transportApiSettings.getResponsesTopic()); } @Override @@ -127,22 +128,22 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE @Override public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { - return new InMemoryTbQueueConsumer<>(coreSettings.getUsageStatsTopic()); + return new InMemoryTbQueueConsumer<>(storage, coreSettings.getUsageStatsTopic()); } @Override public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { - return new InMemoryTbQueueConsumer<>(coreSettings.getOtaPackageTopic()); + return new InMemoryTbQueueConsumer<>(storage, coreSettings.getOtaPackageTopic()); } @Override public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { - return new InMemoryTbQueueProducer<>(coreSettings.getOtaPackageTopic()); + return new InMemoryTbQueueProducer<>(storage, coreSettings.getOtaPackageTopic()); } @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { - return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic()); + return new InMemoryTbQueueProducer<>(storage, coreSettings.getUsageStatsTopic()); } @Scheduled(fixedRateString = "${queue.in_memory.stats.print-interval-ms:60000}") diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java index 252b70459e..9d4116d9ae 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java @@ -31,6 +31,7 @@ import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.memory.InMemoryStorage; import org.thingsboard.server.queue.memory.InMemoryTbQueueConsumer; import org.thingsboard.server.queue.memory.InMemoryTbQueueProducer; import org.thingsboard.server.queue.settings.TbQueueCoreSettings; @@ -45,24 +46,27 @@ public class InMemoryTbTransportQueueFactory implements TbTransportQueueFactory private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbServiceInfoProvider serviceInfoProvider; private final TbQueueCoreSettings coreSettings; + private final InMemoryStorage storage; public InMemoryTbTransportQueueFactory(TbQueueTransportApiSettings transportApiSettings, TbQueueTransportNotificationSettings transportNotificationSettings, TbServiceInfoProvider serviceInfoProvider, - TbQueueCoreSettings coreSettings) { + TbQueueCoreSettings coreSettings, + InMemoryStorage storage) { this.transportApiSettings = transportApiSettings; this.transportNotificationSettings = transportNotificationSettings; this.serviceInfoProvider = serviceInfoProvider; this.coreSettings = coreSettings; + this.storage = storage; } @Override public TbQueueRequestTemplate, TbProtoQueueMsg> createTransportApiRequestTemplate() { InMemoryTbQueueProducer> producerTemplate = - new InMemoryTbQueueProducer<>(transportApiSettings.getRequestsTopic()); + new InMemoryTbQueueProducer<>(storage, transportApiSettings.getRequestsTopic()); InMemoryTbQueueConsumer> consumerTemplate = - new InMemoryTbQueueConsumer<>(transportApiSettings.getResponsesTopic() + "." + serviceInfoProvider.getServiceId()); + new InMemoryTbQueueConsumer<>(storage, transportApiSettings.getResponsesTopic() + "." + serviceInfoProvider.getServiceId()); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> templateBuilder = DefaultTbQueueRequestTemplate.builder(); @@ -88,22 +92,22 @@ public class InMemoryTbTransportQueueFactory implements TbTransportQueueFactory @Override public TbQueueProducer> createRuleEngineMsgProducer() { - return new InMemoryTbQueueProducer<>(transportApiSettings.getRequestsTopic()); + return new InMemoryTbQueueProducer<>(storage, transportApiSettings.getRequestsTopic()); } @Override public TbQueueProducer> createTbCoreMsgProducer() { - return new InMemoryTbQueueProducer<>(coreSettings.getTopic()); + return new InMemoryTbQueueProducer<>(storage, coreSettings.getTopic()); } @Override public TbQueueConsumer> createTransportNotificationsConsumer() { - return new InMemoryTbQueueConsumer<>(transportNotificationSettings.getNotificationsTopic() + "." + serviceInfoProvider.getServiceId()); + return new InMemoryTbQueueConsumer<>(storage, transportNotificationSettings.getNotificationsTopic() + "." + serviceInfoProvider.getServiceId()); } @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { - return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic()); + return new InMemoryTbQueueProducer<>(storage, coreSettings.getUsageStatsTopic()); } } diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/memory/DefaultInMemoryStorageTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/memory/DefaultInMemoryStorageTest.java new file mode 100644 index 0000000000..b269e55888 --- /dev/null +++ b/common/queue/src/test/java/org/thingsboard/server/queue/memory/DefaultInMemoryStorageTest.java @@ -0,0 +1,115 @@ +/** + * Copyright © 2016-2022 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.queue.memory; + +import com.google.gson.Gson; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.thingsboard.server.queue.TbQueueMsg; +import org.thingsboard.server.queue.common.DefaultTbQueueMsg; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +@Slf4j +public class DefaultInMemoryStorageTest { + static final int MAX_POLL_SIZE = 1000; + final Gson gson = new Gson(); + final String topic = "tb_core_notification.tb-node-0"; + + InMemoryStorage storage = new DefaultInMemoryStorage(); + + @Test + public void givenStorage_whenGetLagTotal_thenReturnInteger() throws InterruptedException { + assertThat(storage.getLagTotal()).isEqualTo(0); + storage.put("main", mock(TbQueueMsg.class)); + assertThat(storage.getLagTotal()).isEqualTo(1); + storage.put("main", mock(TbQueueMsg.class)); + assertThat(storage.getLagTotal()).isEqualTo(2); + storage.put("hp", mock(TbQueueMsg.class)); + assertThat(storage.getLagTotal()).isEqualTo(3); + storage.get("main"); + assertThat(storage.getLagTotal()).isEqualTo(1); + } + + @Test + public void givenQueueWithMoreThenBatchSize_whenPoll_thenReturnFullListAndSecondList() throws InterruptedException { + List msgs = new ArrayList<>(MAX_POLL_SIZE + 1); + for (int i = 0; i < MAX_POLL_SIZE + 1; i++) { + DefaultTbQueueMsg msg = gson.fromJson("{\"key\": \"" + UUID.randomUUID() + "\"}", DefaultTbQueueMsg.class); + msgs.add(msg); + storage.put(topic, msg); + } + + assertThat(storage.getLagTotal()).as("total lag is 1001").isEqualTo(MAX_POLL_SIZE + 1); + assertThat(storage.get(topic)).as("poll exactly 1000 msgs").isEqualTo(msgs.subList(0, MAX_POLL_SIZE)); + assertThat(storage.get(topic)).as("poll last 1 message").isEqualTo(msgs.subList(MAX_POLL_SIZE, MAX_POLL_SIZE + 1)); + assertThat(storage.getLagTotal()).as("total lag is zero").isEqualTo(0); + } + + private void testPollOnce(final int msgCount) throws InterruptedException { + List msgs = new ArrayList<>(msgCount); + for (int i = 0; i < msgCount; i++) { + DefaultTbQueueMsg msg = gson.fromJson("{\"key\": \"" + UUID.randomUUID() + "\"}", DefaultTbQueueMsg.class); + msgs.add(msg); + storage.put(topic, msg); + } + + assertThat(storage.getLagTotal()).as("total lag before poll").isEqualTo(msgCount); + assertThat(storage.get(topic)).as("polled exactly msgs").isEqualTo(msgs.subList(0, msgCount)); + assertThat(storage.getLagTotal()).as("final lag is zero").isEqualTo(0); + } + + @Test + public void givenQueueWithExactBatchSize_whenPoll_thenReturnExactBatchSizeList() throws InterruptedException { + testPollOnce(MAX_POLL_SIZE); + } + + @Test + public void givenQueueWithExactBatchSizeMinusOne_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { + testPollOnce(MAX_POLL_SIZE - 1); + } + + @Test + public void givenQueueWithExactBatchSizeMinusTen_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { + testPollOnce(MAX_POLL_SIZE - 10); + } + + @Test + public void givenQueueEmpty_whenPoll_thenReturnEmptyList() throws InterruptedException { + testPollOnce(0); + } + + @Test + public void givenQueueWithSingleMessage_whenPoll_thenReturnSingletonList() throws InterruptedException { + testPollOnce(1); + } + + @Test + public void givenQueueWithTwoMessages_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { + testPollOnce(2); + } + + @Test + public void givenQueueWithTenMessages_whenPoll_thenReturnCorrectSizeList() throws InterruptedException { + testPollOnce(10); + } + +} diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/memory/InMemoryStorageTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/memory/InMemoryStorageTest.java deleted file mode 100644 index 84858786b4..0000000000 --- a/common/queue/src/test/java/org/thingsboard/server/queue/memory/InMemoryStorageTest.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright © 2016-2022 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.queue.memory; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.thingsboard.server.queue.TbQueueMsg; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; - -public class InMemoryStorageTest { - - InMemoryStorage storage = InMemoryStorage.getInstance(); - - @Before - public void setUp() { - storage.cleanup(); - } - - @After - public void tearDown() { - storage.cleanup(); - } - - @Test - public void givenStorage_whenGetLagTotal_thenReturnInteger() throws InterruptedException { - assertThat(storage.getLagTotal()).isEqualTo(0); - storage.put("main", mock(TbQueueMsg.class)); - assertThat(storage.getLagTotal()).isEqualTo(1); - storage.put("main", mock(TbQueueMsg.class)); - assertThat(storage.getLagTotal()).isEqualTo(2); - storage.put("hp", mock(TbQueueMsg.class)); - assertThat(storage.getLagTotal()).isEqualTo(3); - storage.get("main"); - assertThat(storage.getLagTotal()).isEqualTo(1); - storage.cleanup(); - assertThat(storage.getLagTotal()).isEqualTo(0); - } -} \ No newline at end of file diff --git a/common/stats/pom.xml b/common/stats/pom.xml index 5e3760dd36..7802793d64 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index cde1ba8d20..98cf0b807a 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java index 45e5009a7b..3d4a4ee053 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -153,7 +153,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { private String validatePayload(UUID sessionId, Request inbound, boolean isEmptyPayloadAllowed) throws AdaptorException { String payload = inbound.getPayloadString(); if (payload == null) { - log.warn("[{}] Payload is empty!", sessionId); + log.debug("[{}] Payload is empty!", sessionId); if (!isEmptyPayloadAllowed) { throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); } diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index 8f92b3ff30..1096d58f2a 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index 26fd7d4fb8..2fb1f533aa 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index 15bf71b917..886ef918f3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -27,6 +27,7 @@ import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider; import org.eclipse.leshan.server.model.StandardBootstrapModelProvider; import org.eclipse.leshan.server.security.BootstrapSecurityStore; @@ -35,6 +36,7 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapConfigStoreTaskProvider; import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore; +import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapTaskProvider; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; import java.util.ArrayList; @@ -48,10 +50,10 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LO @Slf4j public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { - private BootstrapSecurityStore bsSecurityStore; - private SecurityChecker securityChecker; - private BootstrapTaskProvider tasksProvider; - private LwM2mBootstrapModelProvider modelProvider; + private final BootstrapSecurityStore bsSecurityStore; + private final SecurityChecker securityChecker; + private final LwM2MBootstrapTaskProvider tasksProvider; + private final LwM2mBootstrapModelProvider modelProvider; private TransportService transportService; /** @@ -73,7 +75,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession * @param securityChecker used to accept or refuse new {@link BootstrapSession}. */ public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker, - BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) { + LwM2MBootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) { super(bsSecurityStore, securityChecker, tasksProvider, modelProvider); this.bsSecurityStore = bsSecurityStore; this.securityChecker = securityChecker; @@ -100,7 +102,12 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession } DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); if (authorized) { - this.sendLogs (request.getEndpointName(), + try { + this.tasksProvider.put(session.getEndpoint()); + } catch (InvalidConfigurationException e){ + log.error("Failed put to lwM2MBootstrapSessionClients by endpoint [{}]", request.getEndpointName(), e); + } + this.sendLogs(request.getEndpointName(), String.format("%s: Bootstrap session started...", LOG_LWM2M_INFO, request.getEndpointName())); } return session; @@ -108,7 +115,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession @Override public boolean hasConfigFor(BootstrapSession session) { - BootstrapTaskProvider.Tasks firstTasks = tasksProvider.getTasks(session, null); + BootstrapTaskProvider.Tasks firstTasks = this.tasksProvider.getTasks(session, null); if (firstTasks == null) { return false; } @@ -147,7 +154,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession return requestsToSend.remove(0); } else { if (session.hasMoreTasks()) { - BootstrapTaskProvider.Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses()); + BootstrapTaskProvider.Tasks nextTasks = this.tasksProvider.getTasks(session, session.getResponses()); if (nextTasks == null) { session.setMoreTasks(false); return new BootstrapFinishRequest(); @@ -178,6 +185,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession // on success for bootstrap finish request we stop the session this.sendLogs(bsSession.getEndpoint(), String.format("%s: receives success response for bootstrap finish.", LOG_LWM2M_INFO)); + this.tasksProvider.remove(bsSession.getEndpoint()); return BootstrapPolicy.finished(); } } @@ -199,6 +207,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession // on response error for bootstrap finish request we stop the session this.sendLogs(bsSession.getEndpoint(), String.format("%s: error response for request bootstrap finish. Stop the session: %s", LOG_LWM2M_ERROR, bsSession.toString())); + this.tasksProvider.remove(bsSession.getEndpoint()); return BootstrapPolicy.failed(); } } @@ -215,12 +224,14 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession @Override public void end(BootstrapSession bsSession) { this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO)); + this.tasksProvider.remove(bsSession.getEndpoint()); } @Override public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) { this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session failed because of %s", LOG_LWM2M_ERROR, cause.toString())); + this.tasksProvider.remove(bsSession.getEndpoint()); } private void sendLogs(String endpointName, String logMsg) { diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapClientInstanceIds.java similarity index 61% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapClientInstanceIds.java index 61079eadc0..85aee2b9b9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcBackwardCompatibilityIntegrationTest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapClientInstanceIds.java @@ -13,12 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.mqtt.rpc.sql; +package org.thingsboard.server.transport.lwm2m.bootstrap.store; -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest; +import lombok.Data; +import java.util.HashMap; +import java.util.Map; -@DaoSqlTest -public class MqttServerSideRpcBackwardCompatibilityIntegrationTest extends AbstractMqttServerSideRpcBackwardCompatibilityIntegrationTest { +@Data +public class LwM2MBootstrapClientInstanceIds { + + /** + * Map + */ + private Map securityInstances = new HashMap<>(); + private Map serverInstances = new HashMap<>(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java index 9100df57a1..50aff2a5bd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java @@ -30,8 +30,8 @@ import org.eclipse.leshan.core.response.LwM2mResponse; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.BootstrapSession; -import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; import org.eclipse.leshan.server.bootstrap.BootstrapUtil; +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import java.math.BigInteger; import java.util.ArrayList; @@ -42,28 +42,36 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_DEFAULT_SHORT_ID; @Slf4j -public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvider { +public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTaskProvider { + + protected final ReadWriteLock readWriteLock; + protected final Lock writeLock; private BootstrapConfigStore store; private Map supportedObjects; /** - * Map + * Map */ - protected Map securityInstances; - protected Map serverInstances; - protected Integer bootstrapServerIdOld; - protected Integer bootstrapServerIdNew; + protected Map lwM2MBootstrapSessionClients; public LwM2MBootstrapConfigStoreTaskProvider(BootstrapConfigStore store) { this.store = store; + this.lwM2MBootstrapSessionClients = new ConcurrentHashMap<>(); + readWriteLock = new ReentrantReadWriteLock(); + writeLock = readWriteLock.writeLock(); } @Override @@ -91,13 +99,13 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0); if (discoverResponse.isSuccess()) { this.initAfterBootstrapDiscover(discoverResponse); - findSecurityInstanceId(discoverResponse.getObjectLinks()); + findSecurityInstanceId(discoverResponse.getObjectLinks(), session.getEndpoint()); } else { log.warn( "Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}", discoverResponse, session); } - if (this.securityInstances.get(0) == null) { + if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID) == null) { log.error( "Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}", discoverResponse, session); @@ -109,8 +117,12 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi return tasks; } BootstrapReadResponse readResponse = (BootstrapReadResponse) previousResponse.get(0); + Integer bootstrapServerIdOld = null; if (readResponse.isSuccess()) { - findServerInstanceId(readResponse); + findServerInstanceId(readResponse, session.getEndpoint()); + if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().size() > 0 && this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getServerInstances().size() > 0) { + bootstrapServerIdOld = this.findBootstrapServerId(session.getEndpoint()); + } } else { log.warn( "Bootstrap ReadResponse return error {} : to continue bootstrap session without find Server Instance Id. {}", @@ -118,7 +130,8 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi } // create requests from config tasks.requestsToSend = this.toRequests(config, - config.contentFormat != null ? config.contentFormat : session.getContentFormat()); + config.contentFormat != null ? config.contentFormat : session.getContentFormat(), + bootstrapServerIdOld, session.getEndpoint()); } else { // create requests from config tasks.requestsToSend = BootstrapUtil.toRequests(config, @@ -132,9 +145,15 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi return config.autoIdForSecurityObject; } - protected void findSecurityInstanceId(Link[] objectLinks) { + /** + * "Short Server ID": This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'. + * The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server. + * "Short Server ID": + * - Link Instance (lwm2m Server) hase linkParams with key = "ssid" value = "shortId" (ver lvm2m = 1.1). + * - Link Instance (bootstrap Server) hase not linkParams with key = "ssid" (ver lvm2m = 1.1). + */ + protected void findSecurityInstanceId(Link[] objectLinks, String endpoint) { log.info("Object after discover: [{}]", objectLinks); - this.securityInstances = new HashMap<>(); for (Link link : objectLinks) { if (link.getUriReference().startsWith("/0/")) { try { @@ -142,15 +161,15 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi if (path.isObjectInstance()) { if (link.getLinkParams().containsKey("ssid")) { int serverId = Integer.parseInt(link.getLinkParams().get("ssid").getUnquoted()); - if (!this.securityInstances.containsKey(serverId)) { - this.securityInstances.put(serverId, path.getObjectInstanceId()); + if (!lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(serverId)) { + lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId()); } else { log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId()); } - this.securityInstances.put(Integer.valueOf(link.getLinkParams().get("ssid").getUnquoted()), path.getObjectInstanceId()); + lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(Integer.valueOf(link.getLinkParams().get("ssid").getUnquoted()), path.getObjectInstanceId()); } else { - if (!this.securityInstances.containsKey(0)) { - this.securityInstances.put(0, path.getObjectInstanceId()); + if (!this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(0)) { + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID, path.getObjectInstanceId()); } else { log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId()); } @@ -164,8 +183,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi } } - protected void findServerInstanceId(BootstrapReadResponse readResponse) { - this.serverInstances = new HashMap<>(); + protected void findServerInstanceId(BootstrapReadResponse readResponse, String endpoint) { try { ((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> { var shId = OPAQUE.equals(instance.getResource(0).getType()) ? new BigInteger((byte[]) instance.getResource(0).getValue()).intValue() : instance.getResource(0).getValue(); @@ -175,23 +193,22 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi } else { shortId = (int) shId; } - serverInstances.put(shortId, instance.getId()); + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().put(shortId, instance.getId()); }); } catch (Exception e) { log.error("Failed find Server Instance Id. ", e); } - if (this.securityInstances != null && this.securityInstances.size() > 0 && this.serverInstances != null && this.serverInstances.size() > 0) { - this.findBootstrapServerId(); - } } - protected void findBootstrapServerId() { - Map filteredMap = this.serverInstances.entrySet() - .stream().filter(x -> !this.securityInstances.containsKey(x.getKey())) + protected Integer findBootstrapServerId(String endpoint) { + Integer bootstrapServerIdOld = null; + Map filteredMap = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().entrySet() + .stream().filter(x -> !this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(x.getKey())) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (filteredMap.size() > 0) { - this.bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get(); + bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get(); } + return bootstrapServerIdOld; } public BootstrapConfigStore getStore() { @@ -213,46 +230,51 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi public List> toRequests(BootstrapConfig bootstrapConfig, - ContentFormat contentFormat) { + ContentFormat contentFormat, + Integer bootstrapServerIdOld, + String endpoint) { List> requests = new ArrayList<>(); Set pathsDelete = new HashSet<>(); List> requestsWrite = new ArrayList<>(); boolean isBsServer = false; boolean isLwServer = false; - /** Map */ + /** Map */ Map instances = new HashMap<>(); + Integer bootstrapServerIdNew = null; // handle security - int id = 0; + int lwm2mSecurityInstanceId = 0; + int bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID); for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) { if (security.bootstrapServer) { - requestsWrite.add(toWriteRequest(this.securityInstances.get(0), security, contentFormat)); + requestsWrite.add(toWriteRequest(bootstrapSecurityInstanceId, security, contentFormat)); isBsServer = true; - this.bootstrapServerIdNew = security.serverId; - instances.put(security.serverId, this.securityInstances.get(0)); + bootstrapServerIdNew = security.serverId; + instances.put(security.serverId, bootstrapSecurityInstanceId); } else { - if (id == this.securityInstances.get(0)) { - id++; + if (lwm2mSecurityInstanceId == bootstrapSecurityInstanceId) { + lwm2mSecurityInstanceId++; } - requestsWrite.add(toWriteRequest(id, security, contentFormat)); - instances.put(security.serverId, id); + requestsWrite.add(toWriteRequest(lwm2mSecurityInstanceId, security, contentFormat)); + instances.put(security.serverId, lwm2mSecurityInstanceId); isLwServer = true; - if (!isBsServer && this.securityInstances.containsKey(security.serverId) && id != this.securityInstances.get(security.serverId)) { - pathsDelete.add("/0/" + this.securityInstances.get(security.serverId)); + if (!isBsServer && this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(security.serverId) && + lwm2mSecurityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId)) { + pathsDelete.add("/0/" + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId)); } /** * If there is an instance in the serverInstances with serverId which we replace in the securityInstances */ // find serverId in securityInstances by id (instance) Integer serverIdOld = null; - for (Map.Entry entry : this.securityInstances.entrySet()) { - if (entry.getValue().equals(id)) { + for (Map.Entry entry : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().entrySet()) { + if (entry.getValue().equals(lwm2mSecurityInstanceId)) { serverIdOld = entry.getKey(); } } - if (!isBsServer && serverIdOld != null && this.serverInstances.containsKey(serverIdOld)) { - pathsDelete.add("/1/" + this.serverInstances.get(serverIdOld)); + if (!isBsServer && serverIdOld != null && this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(serverIdOld)) { + pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(serverIdOld)); } - id++; + lwm2mSecurityInstanceId++; } } // handle server @@ -261,12 +283,13 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi requestsWrite.add(toWriteRequest(securityInstanceId, server.getValue(), contentFormat)); if (!isBsServer) { /** Delete instance if bootstrapServerIdNew not equals bootstrapServerIdOld or securityInstanceBsIdNew not equals serverInstanceBsIdOld */ - if (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew && - (this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) { - pathsDelete.add("/1/" + this.serverInstances.get(this.bootstrapServerIdOld)); + if (bootstrapServerIdNew != null && server.getValue().shortId == bootstrapServerIdNew && + (bootstrapServerIdNew != bootstrapServerIdOld || securityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(bootstrapServerIdOld))) { + pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(bootstrapServerIdOld)); /** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */ - } else if (this.serverInstances.containsKey(server.getValue().shortId) && securityInstanceId != this.serverInstances.get(server.getValue().shortId)) { - pathsDelete.add("/1/" + this.serverInstances.get(server.getValue().shortId)); + } else if (this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(server.getValue().shortId) && + securityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(server.getValue().shortId)) { + pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(server.getValue().shortId)); } } } @@ -288,10 +311,31 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi return (requests); } + private void initSupportedObjectsDefault() { this.supportedObjects = new HashMap<>(); this.supportedObjects.put(0, "1.1"); this.supportedObjects.put(1, "1.1"); this.supportedObjects.put(2, "1.0"); } + + @Override + public void remove(String endpoint) { + writeLock.lock(); + try { + this.lwM2MBootstrapSessionClients.remove(endpoint); + } finally { + writeLock.unlock(); + } + } + + @Override + public void put(String endpoint) throws InvalidConfigurationException { + writeLock.lock(); + try { + this.lwM2MBootstrapSessionClients.put(endpoint, new LwM2MBootstrapClientInstanceIds()); + } finally { + writeLock.unlock(); + } + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcProtoSqlIntegrationTest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapTaskProvider.java similarity index 61% rename from application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcProtoSqlIntegrationTest.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapTaskProvider.java index 169d4d51e5..6bc792fed7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/sql/CoapServerSideRpcProtoSqlIntegrationTest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapTaskProvider.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.coap.rpc.sql; +package org.thingsboard.server.transport.lwm2m.bootstrap.store; -import org.thingsboard.server.transport.coap.rpc.AbstractCoapServerSideRpcProtoIntegrationTest; -import org.thingsboard.server.dao.service.DaoSqlTest; +import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; +import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; +public interface LwM2MBootstrapTaskProvider extends BootstrapTaskProvider { -@DaoSqlTest -public class CoapServerSideRpcProtoSqlIntegrationTest extends AbstractCoapServerSideRpcProtoIntegrationTest { + void put(String endpoint) throws InvalidConfigurationException; + + void remove(String endpoint); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 7860fd94a5..9b49030f7e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -20,11 +20,15 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.scandium.config.DtlsConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; +import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; +import org.eclipse.leshan.core.request.SendRequest; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.send.SendListener; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.DataConstants; @@ -40,6 +44,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PreDestroy; import java.security.cert.X509Certificate; +import java.util.Map; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; @@ -94,6 +99,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { this.server.getRegistrationService().addListener(lhServerCertListener.registrationListener); this.server.getPresenceService().addListener(lhServerCertListener.presenceListener); this.server.getObservationService().addListener(lhServerCertListener.observationListener); + this.server.getSendService().addListener(lhServerCertListener.sendListener); log.info("Started LwM2M transport server."); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 5c0e225f8b..4d4af68fad 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -16,9 +16,11 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.SingleObservation; +import org.eclipse.leshan.core.request.SendRequest; import org.eclipse.leshan.core.response.ObserveCompositeResponse; import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.server.observation.ObservationListener; @@ -26,9 +28,11 @@ import org.eclipse.leshan.server.queue.PresenceListener; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationListener; import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.eclipse.leshan.server.send.SendListener; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Collection; +import java.util.Map; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; @@ -119,4 +123,14 @@ public class LwM2mServerListener { log.trace("Successful start newObservation {}.", ((SingleObservation)observation).getPath()); } }; + + public final SendListener sendListener = new SendListener() { + + @Override + public void dataReceived(Registration registration, Map map, SendRequest sendRequest) { + if (registration != null) { + service.onUpdateValueWithSendRequest(registration, sendRequest); + } + } + }; } 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 05ce126c7e..d73bd646c3 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 @@ -33,11 +33,7 @@ import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.request.CreateRequest; -import org.eclipse.leshan.core.request.ObserveRequest; -import org.eclipse.leshan.core.request.ReadRequest; -import org.eclipse.leshan.core.request.WriteCompositeRequest; -import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.request.*; import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse; @@ -98,16 +94,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -347,12 +334,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE, responseCode); } } - if (clientContext.awake(lwM2MClient)) { - // clientContext.awake calls clientContext.update - log.debug("[{}] Device is awake", lwM2MClient.getEndpoint()); - } else { - clientContext.update(lwM2MClient); - } + tryAwake(lwM2MClient); } } @@ -373,12 +355,37 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl } }); clientContext.update(lwM2MClient); - if (clientContext.awake(lwM2MClient)) { - // clientContext.awake calls clientContext.update - log.debug("[{}] Device is awake", lwM2MClient.getEndpoint()); - } else { - clientContext.update(lwM2MClient); + tryAwake(lwM2MClient); + } + } + + /** + * Sending updated value to thingsboard from SendListener.dataReceived: object, instance, SingleResource or MultipleResource + * + * @param registration - Registration LwM2M Client + * @param sendRequest - sendRequest + */ + @Override + public void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest) { + for(var entry : sendRequest.getNodes().entrySet()) { + LwM2mPath path = entry.getKey(); + LwM2mNode node = entry.getValue(); + LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); + String stringPath = convertObjectIdToVersionedId(path.toString(), registration); + ObjectModel objectModelVersion = lwM2MClient.getObjectModel(stringPath, modelProvider); + if (objectModelVersion != null) { + if (node instanceof LwM2mObject) { + LwM2mObject lwM2mObject = (LwM2mObject) node; + this.updateObjectResourceValue(lwM2MClient, lwM2mObject, stringPath, 0); + } else if (node instanceof LwM2mObjectInstance) { + LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) node; + this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, stringPath, 0); + } else if (node instanceof LwM2mResource) { + LwM2mResource lwM2mResource = (LwM2mResource) node; + this.updateResourcesValue(lwM2MClient, lwM2mResource, stringPath, Mode.UPDATE, 0); + } } + tryAwake(lwM2MClient); } } @@ -989,4 +996,14 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl client.unlock(); } } + + private void tryAwake(LwM2mClient lwM2MClient) { + if (clientContext.awake(lwM2MClient)) { + // clientContext.awake calls clientContext.update + log.debug("[{}] Device is awake", lwM2MClient.getEndpoint()); + } else { + clientContext.update(lwM2MClient); + } + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java index 475d834c60..332bb6ccad 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.uplink; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.CreateRequest; +import org.eclipse.leshan.core.request.SendRequest; import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadCompositeResponse; @@ -46,6 +47,8 @@ public interface LwM2mUplinkMsgHandler { void onUpdateValueAfterReadCompositeResponse(Registration registration, ReadCompositeResponse response); + void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest); + void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index e415858755..ca001e3b83 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -92,6 +92,7 @@ public class LwM2MTransportUtil { public static final String LOG_LWM2M_INFO = "info"; public static final String LOG_LWM2M_ERROR = "error"; public static final String LOG_LWM2M_WARN = "warn"; + public static final int BOOTSTRAP_DEFAULT_SHORT_ID = 0; public enum LwM2MClientStrategy { CLIENT_STRATEGY_1(1, "Read only resources marked as observation"), diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 69fe09a043..c029e7c9ac 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.common.transport 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 a07982d718..f37715efc8 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 @@ -242,9 +242,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName); ctx.close(); } - } catch (RuntimeException | AdaptorException e) { + } catch (RuntimeException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); ctx.close(); + } catch (AdaptorException e) { + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + ctx.close(); } break; case PINGREQ: @@ -354,9 +357,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement default: ack(ctx, msgId); } - } catch (RuntimeException | AdaptorException e) { + } catch (RuntimeException e) { log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); ctx.close(); + } catch (AdaptorException e) { + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + sendAckOrCloseSession(ctx, topicName, msgId); } } @@ -444,7 +450,16 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement ack(ctx, msgId); } } catch (AdaptorException e) { - log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); + sendAckOrCloseSession(ctx, topicName, msgId); + } + } + + private void sendAckOrCloseSession(ChannelHandlerContext ctx, String topicName, int msgId) { + if (deviceSessionCtx.isSendAckOnValidationException() && msgId > 0) { + log.debug("[{}] Send pub ack on invalid publish msg [{}][{}]", sessionId, topicName, msgId); + ctx.writeAndFlush(createMqttPubAckMsg(msgId)); + } else { log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId); ctx.close(); } @@ -749,7 +764,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } } catch (Exception e) { - log.warn("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName); + log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName); } } if (!activityReported) { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 73c0e9a32f..75c76c0cdb 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -61,7 +61,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { try { return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); } catch (IllegalStateException | JsonSyntaxException ex) { - log.warn("Failed to decode post telemetry request", ex); + log.debug("Failed to decode post telemetry request", ex); throw new AdaptorException(ex); } } @@ -72,7 +72,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { try { return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); } catch (IllegalStateException | JsonSyntaxException ex) { - log.warn("Failed to decode post attributes request", ex); + log.debug("Failed to decode post attributes request", ex); throw new AdaptorException(ex); } } @@ -83,7 +83,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { try { return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload); } catch (IllegalStateException | JsonSyntaxException ex) { - log.warn("Failed to decode claim device request", ex); + log.debug("Failed to decode claim device request", ex); throw new AdaptorException(ex); } } @@ -164,7 +164,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { try { return new JsonParser().parse(payload); } catch (JsonSyntaxException ex) { - log.warn("Payload is in incorrect format: {}", payload); + log.debug("Payload is in incorrect format: {}", payload); throw new AdaptorException(ex); } } @@ -186,7 +186,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { } return result.build(); } catch (RuntimeException e) { - log.warn("Failed to decode get attributes request", e); + log.debug("Failed to decode get attributes request", e); throw new AdaptorException(e); } } @@ -198,7 +198,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { String payload = inbound.payload().toString(UTF8); return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); } catch (RuntimeException e) { - log.warn("Failed to decode rpc response", e); + log.debug("Failed to decode rpc response", e); throw new AdaptorException(e); } } @@ -210,7 +210,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { int requestId = getRequestId(topicName, topicBase); return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId); } catch (IllegalStateException | JsonSyntaxException ex) { - log.warn("Failed to decode to server rpc request", ex); + log.debug("Failed to decode to server rpc request", ex); throw new AdaptorException(ex); } } @@ -259,7 +259,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException { String payload = payloadData.toString(UTF8); if (payload == null) { - log.warn("[{}] Payload is empty!", sessionId); + log.debug("[{}] Payload is empty!", sessionId); if (!isEmptyPayloadAllowed) { throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 1f0aef1c64..175bf17c02 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java @@ -52,7 +52,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { try { return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor))); } catch (Exception e) { - log.warn("Failed to decode post telemetry request", e); + log.debug("Failed to decode post telemetry request", e); throw new AdaptorException(e); } } @@ -65,7 +65,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { try { return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor))); } catch (Exception e) { - log.warn("Failed to decode post attributes request", e); + log.debug("Failed to decode post attributes request", e); throw new AdaptorException(e); } } @@ -76,7 +76,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { try { return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes); } catch (InvalidProtocolBufferException e) { - log.warn("Failed to decode claim device request", e); + log.debug("Failed to decode claim device request", e); throw new AdaptorException(e); } } @@ -89,7 +89,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { int requestId = getRequestId(topicName, topicBase); return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId); } catch (InvalidProtocolBufferException e) { - log.warn("Failed to decode get attributes request", e); + log.debug("Failed to decode get attributes request", e); throw new AdaptorException(e); } } @@ -105,7 +105,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor)); return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build(); } catch (Exception e) { - log.warn("Failed to decode rpc response", e); + log.debug("Failed to decode rpc response", e); throw new AdaptorException(e); } } @@ -118,7 +118,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { int requestId = getRequestId(topicName, topicBase); return ProtoConverter.convertToServerRpcRequest(bytes, requestId); } catch (InvalidProtocolBufferException e) { - log.warn("Failed to decode to server rpc request", e); + log.debug("Failed to decode to server rpc request", e); throw new AdaptorException(e); } } @@ -129,7 +129,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { try { return ProtoConverter.convertToProvisionRequestMsg(bytes); } catch (InvalidProtocolBufferException ex) { - log.warn("Failed to decode provision request", ex); + log.debug("Failed to decode provision request", ex); throw new AdaptorException(ex); } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java index c7a70c6d44..c6f605c9dd 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.mqtt.limits; +import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.haproxy.HAProxyMessage; @@ -60,7 +61,15 @@ public class ProxyIpFilter extends ChannelInboundHandlerAdapter { private void closeChannel(ChannelHandlerContext ctx) { while (ctx.pipeline().last() != this) { - ctx.pipeline().removeLast(); + ChannelHandler handler = ctx.pipeline().removeLast(); + if (handler instanceof ChannelInboundHandlerAdapter) { + try { + ((ChannelInboundHandlerAdapter) handler).channelUnregistered(ctx); + } catch (Exception e) { + log.error("Failed to unregister channel: [{}]", ctx, e); + } + } + } ctx.pipeline().remove(this); ctx.close(); 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 ed7a461256..2163610b38 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 @@ -84,6 +84,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private volatile MqttTransportAdaptor adaptor; private volatile boolean jsonPayloadFormatCompatibilityEnabled; private volatile boolean useJsonPayloadFormatForDefaultDownlinkTopics; + private volatile boolean sendAckOnValidationException; @Getter @Setter @@ -115,6 +116,10 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return payloadType.equals(TransportPayloadType.JSON); } + public boolean isSendAckOnValidationException() { + return sendAckOnValidationException; + } + public Descriptors.Descriptor getTelemetryDynamicMsgDescriptor() { return telemetryDynamicMessageDescriptor; } @@ -152,6 +157,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); attributesTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); + sendAckOnValidationException = mqttConfig.isSendAckOnValidationException(); if (TransportPayloadType.PROTOBUF.equals(payloadType)) { ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; updateDynamicMessageDescriptors(protoTransportPayloadConfig); @@ -162,6 +168,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); attributesTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); payloadType = TransportPayloadType.JSON; + sendAckOnValidationException = false; } updateAdaptor(); } diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 93e8bd310c..a061c963f4 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 6ea0d6f06d..ee1d8f2a0d 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 9195cfe132..8b3f9feb22 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.common.transport 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 01966bbef6..5b29d3c3ff 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 @@ -191,7 +191,7 @@ public class DefaultTransportService implements TransportService { protected ExecutorService transportCallbackExecutor; private ExecutorService mainConsumerExecutor; - private final ConcurrentMap sessions = new ConcurrentHashMap<>(); + public final ConcurrentMap sessions = new ConcurrentHashMap<>(); private final ConcurrentMap sessionsActivity = new ConcurrentHashMap<>(); private final Map toServerRpcPendingMap = new ConcurrentHashMap<>(); diff --git a/common/util/pom.xml b/common/util/pom.xml index f7c3602bca..147497ef04 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT common org.thingsboard.common @@ -84,6 +84,10 @@ awaitility test + + org.thingsboard.common + data + diff --git a/common/util/src/main/java/org/thingsboard/common/util/KvUtil.java b/common/util/src/main/java/org/thingsboard/common/util/KvUtil.java new file mode 100644 index 0000000000..ba86fda769 --- /dev/null +++ b/common/util/src/main/java/org/thingsboard/common/util/KvUtil.java @@ -0,0 +1,89 @@ +/** + * Copyright © 2016-2022 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.common.util; + +import org.thingsboard.server.common.data.kv.KvEntry; + +public class KvUtil { + + public static String getStringValue(KvEntry entry) { + switch (entry.getDataType()) { + case LONG: + return entry.getLongValue().map(String::valueOf).orElse(null); + case DOUBLE: + return entry.getDoubleValue().map(String::valueOf).orElse(null); + case BOOLEAN: + return entry.getBooleanValue().map(String::valueOf).orElse(null); + case STRING: + return entry.getStrValue().orElse(""); + case JSON: + return entry.getJsonValue().orElse(""); + default: + return null; + } + } + + public static Double getDoubleValue(KvEntry entry) { + switch (entry.getDataType()) { + case LONG: + return entry.getLongValue().map(Long::doubleValue).orElse(null); + case DOUBLE: + return entry.getDoubleValue().orElse(null); + case BOOLEAN: + return entry.getBooleanValue().map(e -> e ? 1.0 : 0).orElse(null); + case STRING: + try { + return Double.parseDouble(entry.getStrValue().orElse("")); + } catch (RuntimeException e) { + return null; + } + case JSON: + try { + return Double.parseDouble(entry.getJsonValue().orElse("")); + } catch (RuntimeException e) { + return null; + } + default: + return null; + } + } + + public static Boolean getBoolValue(KvEntry entry) { + switch (entry.getDataType()) { + case LONG: + return entry.getLongValue().map(e -> e != 0).orElse(null); + case DOUBLE: + return entry.getDoubleValue().map(e -> e != 0).orElse(null); + case BOOLEAN: + return entry.getBooleanValue().orElse(null); + case STRING: + try { + return Boolean.parseBoolean(entry.getStrValue().orElse("")); + } catch (RuntimeException e) { + return null; + } + case JSON: + try { + return Boolean.parseBoolean(entry.getJsonValue().orElse("")); + } catch (RuntimeException e) { + return null; + } + default: + return null; + } + } + +} diff --git a/dao/pom.xml b/dao/pom.xml index fdff417fe6..17d982d02e 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard dao @@ -220,14 +220,6 @@ org.springframework spring-context-support - - org.springframework.data - spring-data-redis - - - redis.clients - jedis - org.elasticsearch.client rest diff --git a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java index 56ae8392e9..932312f96e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsDaoConfig.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -23,9 +22,10 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import org.thingsboard.server.dao.util.TbAutoConfiguration; @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @ComponentScan({"org.thingsboard.server.dao.sqlts.hsql"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.hsql"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.ts"}) diff --git a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsLatestDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsLatestDaoConfig.java index f615a18402..2012ca5a82 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/HsqlTsLatestDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/HsqlTsLatestDaoConfig.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -23,9 +22,10 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thingsboard.server.dao.util.HsqlDao; import org.thingsboard.server.dao.util.SqlTsLatestDao; +import org.thingsboard.server.dao.util.TbAutoConfiguration; @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @ComponentScan({"org.thingsboard.server.dao.sqlts.hsql"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.hsql", "org.thingsboard.server.dao.sqlts.latest"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"}) diff --git a/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java index d71b989d56..de695a9035 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/JpaDaoConfig.java @@ -15,18 +15,18 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; +import org.thingsboard.server.dao.util.TbAutoConfiguration; /** * @author Valerii Sosliuk */ @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @ComponentScan("org.thingsboard.server.dao.sql") @EnableJpaRepositories("org.thingsboard.server.dao.sql") @EntityScan("org.thingsboard.server.dao.model.sql") diff --git a/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java index 9c717ec396..06c8cf4a5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsDaoConfig.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -23,9 +22,10 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import org.thingsboard.server.dao.util.TbAutoConfiguration; @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @ComponentScan({"org.thingsboard.server.dao.sqlts.psql", "org.thingsboard.server.dao.sqlts.insert.psql"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.ts", "org.thingsboard.server.dao.sqlts.insert.psql"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.ts"}) diff --git a/dao/src/main/java/org/thingsboard/server/dao/PsqlTsLatestDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsLatestDaoConfig.java index 73ff6f63fe..9d8d625e78 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/PsqlTsLatestDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/PsqlTsLatestDaoConfig.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @@ -23,9 +22,10 @@ import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsLatestDao; +import org.thingsboard.server.dao.util.TbAutoConfiguration; @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @ComponentScan({"org.thingsboard.server.dao.sqlts.psql"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.psql", "org.thingsboard.server.dao.sqlts.latest"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"}) diff --git a/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java index e360f84b53..6964ba9dd1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/SqlTimeseriesDaoConfig.java @@ -15,16 +15,15 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; -import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsOrTsLatestAnyDao; +import org.thingsboard.server.dao.util.TbAutoConfiguration; @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.dictionary"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.dictionary"}) @EnableTransactionManagement diff --git a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java index 7f73e0dfcc..0e4d4750f9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/TimescaleDaoConfig.java @@ -15,17 +15,17 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.TbAutoConfiguration; import org.thingsboard.server.dao.util.TimescaleDBTsDao; @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @ComponentScan({"org.thingsboard.server.dao.sqlts.timescale"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.timescale", "org.thingsboard.server.dao.sqlts.insert.timescale"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.timescale"}) diff --git a/dao/src/main/java/org/thingsboard/server/dao/TimescaleTsLatestDaoConfig.java b/dao/src/main/java/org/thingsboard/server/dao/TimescaleTsLatestDaoConfig.java index a9e4b6baab..02082085ba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/TimescaleTsLatestDaoConfig.java +++ b/dao/src/main/java/org/thingsboard/server/dao/TimescaleTsLatestDaoConfig.java @@ -15,17 +15,17 @@ */ package org.thingsboard.server.dao; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.util.TbAutoConfiguration; import org.thingsboard.server.dao.util.TimescaleDBTsLatestDao; @Configuration -@EnableAutoConfiguration +@TbAutoConfiguration @ComponentScan({"org.thingsboard.server.dao.sqlts.timescale"}) @EnableJpaRepositories({"org.thingsboard.server.dao.sqlts.insert.latest.psql", "org.thingsboard.server.dao.sqlts.latest"}) @EntityScan({"org.thingsboard.server.dao.model.sqlts.latest"}) diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 08f4576449..714e131025 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -25,9 +25,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; @@ -49,9 +47,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantDao; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -59,7 +55,6 @@ import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -82,10 +77,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ private AlarmDao alarmDao; @Autowired - private TenantDao tenantDao; + private EntityService entityService; @Autowired - private EntityService entityService; + private DataValidator alarmDataValidator; protected ExecutorService readResultsProcessingExecutor; @@ -412,32 +407,4 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ ListenableFuture entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()); return Futures.transform(entity, function, readResultsProcessingExecutor); } - - private DataValidator alarmDataValidator = - new DataValidator<>() { - - @Override - protected void validateDataImpl(TenantId tenantId, Alarm alarm) { - if (StringUtils.isEmpty(alarm.getType())) { - throw new DataValidationException("Alarm type should be specified!"); - } - if (alarm.getOriginator() == null) { - throw new DataValidationException("Alarm originator should be specified!"); - } - if (alarm.getSeverity() == null) { - throw new DataValidationException("Alarm severity should be specified!"); - } - if (alarm.getStatus() == null) { - throw new DataValidationException("Alarm status should be specified!"); - } - if (alarm.getTenantId() == null) { - throw new DataValidationException("Alarm should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(alarm.getTenantId(), alarm.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Alarm is referencing to non-existent tenant!"); - } - } - } - }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index e92f1fd9ce..bf8d75d27a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -22,18 +22,12 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; @@ -48,17 +42,13 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.cache.EntitiesCacheManager; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -67,7 +57,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE; import static org.thingsboard.server.dao.DaoUtil.toUUIDs; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validatePageLink; @@ -86,17 +75,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ private AssetDao assetDao; @Autowired - private TenantDao tenantDao; + private EntitiesCacheManager cacheManager; @Autowired - private CustomerDao customerDao; - - @Autowired - private CacheManager cacheManager; - - @Autowired - @Lazy - private TbTenantProfileCache tenantProfileCache; + private DataValidator assetValidator; @Override public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) { @@ -178,16 +160,11 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e); } - removeAssetFromCacheByName(asset.getTenantId(), asset.getName()); + cacheManager.removeAssetFromCacheByName(asset.getTenantId(), asset.getName()); assetDao.removeById(tenantId, assetId.getId()); } - private void removeAssetFromCacheByName(TenantId tenantId, String name) { - Cache cache = cacheManager.getCache(ASSET_CACHE); - cache.evict(Arrays.asList(tenantId, name)); - } - @Override public PageData findAssetsByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); @@ -381,60 +358,6 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ return assetDao.findAssetsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink); } - private DataValidator assetValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, Asset asset) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - if (!TB_SERVICE_QUEUE.equals(asset.getType())) { - long maxAssets = profileConfiguration.getMaxAssets(); - validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); - } - } - - @Override - protected void validateUpdate(TenantId tenantId, Asset asset) { - Asset old = assetDao.findById(asset.getTenantId(), asset.getId().getId()); - if (old == null) { - throw new DataValidationException("Can't update non existing asset!"); - } - if (!old.getName().equals(asset.getName())) { - removeAssetFromCacheByName(tenantId, old.getName()); - } - } - - @Override - protected void validateDataImpl(TenantId tenantId, Asset asset) { - if (StringUtils.isEmpty(asset.getType())) { - throw new DataValidationException("Asset type should be specified!"); - } - if (StringUtils.isEmpty(asset.getName())) { - throw new DataValidationException("Asset name should be specified!"); - } - if (asset.getTenantId() == null) { - throw new DataValidationException("Asset should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(tenantId, asset.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Asset is referencing to non-existent tenant!"); - } - } - if (asset.getCustomerId() == null) { - asset.setCustomerId(new CustomerId(NULL_UUID)); - } else if (!asset.getCustomerId().getId().equals(NULL_UUID)) { - Customer customer = customerDao.findById(tenantId, asset.getCustomerId().getId()); - if (customer == null) { - throw new DataValidationException("Can't assign asset to non-existent customer!"); - } - if (!customer.getTenantId().equals(asset.getTenantId())) { - throw new DataValidationException("Can't assign asset to customer from different tenant!"); - } - } - } - }; - private PaginatedRemover tenantAssetsRemover = new PaginatedRemover() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index bfd591ffa4..6e26f4343d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.audit.ActionStatus; @@ -47,9 +48,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.audit.sink.AuditLogSink; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.entity.EntityService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.common.util.JacksonUtil; import java.io.PrintWriter; import java.io.StringWriter; @@ -80,6 +79,9 @@ public class AuditLogServiceImpl implements AuditLogService { @Autowired private AuditLogSink auditLogSink; + @Autowired + private DataValidator auditLogValidator; + @Override public PageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List actionTypes, TimePageLink pageLink) { log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink); @@ -389,19 +391,4 @@ public class AuditLogServiceImpl implements AuditLogService { return Futures.allAsList(futures); } - private DataValidator auditLogValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId tenantId, AuditLog auditLog) { - if (auditLog.getEntityId() == null) { - throw new DataValidationException("Entity Id should be specified!"); - } - if (auditLog.getTenantId() == null) { - throw new DataValidationException("Tenant Id should be specified!"); - } - if (auditLog.getUserId() == null) { - throw new DataValidationException("User Id should be specified!"); - } - } - }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java new file mode 100644 index 0000000000..19842eacc4 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2022 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.dao.cache; + +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; + +public interface EntitiesCacheManager { + + void removeDeviceFromCacheByName(TenantId tenantId, String name); + + void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId); + + void removeAssetFromCacheByName(TenantId tenantId, String name); + + void removeEdgeFromCacheByName(TenantId tenantId, String name); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java new file mode 100644 index 0000000000..2a28f07424 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java @@ -0,0 +1,64 @@ +/** + * Copyright © 2016-2022 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.dao.cache; + +import lombok.AllArgsConstructor; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Arrays; + +import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE; +import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE; +import static org.thingsboard.server.common.data.CacheConstants.EDGE_CACHE; + +@Component +@AllArgsConstructor +public class EntitiesCacheManagerImpl implements EntitiesCacheManager { + + private final CacheManager cacheManager; + + @Override + public void removeDeviceFromCacheByName(TenantId tenantId, String name) { + Cache cache = cacheManager.getCache(DEVICE_CACHE); + cache.evict(Arrays.asList(tenantId, name)); + } + + @Override + public void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) { + if (deviceId == null) { + return; + } + Cache cache = cacheManager.getCache(DEVICE_CACHE); + cache.evict(Arrays.asList(tenantId, deviceId)); + } + + @Override + public void removeAssetFromCacheByName(TenantId tenantId, String name) { + Cache cache = cacheManager.getCache(ASSET_CACHE); + cache.evict(Arrays.asList(tenantId, name)); + } + + @Override + public void removeEdgeFromCacheByName(TenantId tenantId, String name) { + Cache cache = cacheManager.getCache(EDGE_CACHE); + cache.evict(Arrays.asList(tenantId, name)); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java index 845665f0c4..3f6ee3cbb0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java @@ -21,11 +21,9 @@ import com.github.fge.jsonschema.core.report.ProcessingReport; import com.github.fge.jsonschema.main.JsonSchemaFactory; import com.github.fge.jsonschema.main.JsonValidator; 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.id.ComponentDescriptorId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -49,6 +47,9 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic @Autowired private ComponentDescriptorDao componentDescriptorDao; + @Autowired + private DataValidator componentValidator; + @Override public ComponentDescriptor saveComponent(TenantId tenantId, ComponentDescriptor component) { componentValidator.validate(component, data -> TenantId.SYS_TENANT_ID); @@ -100,23 +101,4 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic throw new IncorrectParameterException(e.getMessage(), e); } } - - private DataValidator componentValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId tenantId, ComponentDescriptor plugin) { - if (plugin.getType() == null) { - throw new DataValidationException("Component type should be specified!"); - } - if (plugin.getScope() == null) { - throw new DataValidationException("Component scope should be specified!"); - } - if (StringUtils.isEmpty(plugin.getName())) { - throw new DataValidationException("Component name should be specified!"); - } - if (StringUtils.isEmpty(plugin.getClazz())) { - throw new DataValidationException("Component clazz should be specified!"); - } - } - }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index 1b21569d54..7f247f5ba2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -19,30 +19,22 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; 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.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.CustomerId; 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.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.user.UserService; @@ -55,7 +47,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @Slf4j public class CustomerServiceImpl extends AbstractEntityService implements CustomerService { - private static final String PUBLIC_CUSTOMER_TITLE = "Public"; + public static final String PUBLIC_CUSTOMER_TITLE = "Public"; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @@ -65,9 +57,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @Autowired private UserService userService; - @Autowired - private TenantDao tenantDao; - @Autowired private AssetService assetService; @@ -84,8 +73,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom private ApiUsageStateService apiUsageStateService; @Autowired - @Lazy - private TbTenantProfileCache tenantProfileCache; + private DataValidator customerValidator; @Override public Customer findCustomerById(TenantId tenantId, CustomerId customerId) { @@ -171,56 +159,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom customersByTenantRemover.removeEntities(tenantId, tenantId); } - private DataValidator customerValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, Customer customer) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxCustomers = profileConfiguration.getMaxCustomers(); - - validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER); - customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( - c -> { - throw new DataValidationException("Customer with such title already exists!"); - } - ); - } - - @Override - protected void validateUpdate(TenantId tenantId, Customer customer) { - customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( - c -> { - if (!c.getId().equals(customer.getId())) { - throw new DataValidationException("Customer with such title already exists!"); - } - } - ); - } - - @Override - protected void validateDataImpl(TenantId tenantId, Customer customer) { - if (StringUtils.isEmpty(customer.getTitle())) { - throw new DataValidationException("Customer title should be specified!"); - } - if (customer.getTitle().equals(PUBLIC_CUSTOMER_TITLE)) { - throw new DataValidationException("'Public' title for customer is system reserved!"); - } - if (!StringUtils.isEmpty(customer.getEmail())) { - validateEmail(customer.getEmail()); - } - if (customer.getTenantId() == null) { - throw new DataValidationException("Customer should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(tenantId, customer.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Customer is referencing to non-existent tenant!"); - } - } - } - }; - private PaginatedRemover customersByTenantRemover = new PaginatedRemover() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index f89c1b0450..ee5c44e20f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -17,16 +17,12 @@ package org.thingsboard.server.dao.dashboard; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; @@ -36,7 +32,6 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.entity.AbstractEntityService; @@ -44,8 +39,6 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -61,9 +54,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @Autowired private DashboardInfoDao dashboardInfoDao; - @Autowired - private TenantDao tenantDao; - @Autowired private CustomerDao customerDao; @@ -71,8 +61,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb private EdgeDao edgeDao; @Autowired - @Lazy - private TbTenantProfileCache tenantProfileCache; + private DataValidator dashboardValidator; @Override public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) { @@ -290,32 +279,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name); } - private DataValidator dashboardValidator = - new DataValidator() { - @Override - protected void validateCreate(TenantId tenantId, Dashboard data) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxDashboards = profileConfiguration.getMaxDashboards(); - validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD); - } - - @Override - protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) { - if (StringUtils.isEmpty(dashboard.getTitle())) { - throw new DataValidationException("Dashboard title should be specified!"); - } - if (dashboard.getTenantId() == null) { - throw new DataValidationException("Dashboard should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(tenantId, dashboard.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Dashboard is referencing to non-existent tenant!"); - } - } - } - }; - private PaginatedRemover tenantDashboardsRemover = new PaginatedRemover() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index 1be93dd7b9..ff92c330e0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -25,21 +25,19 @@ import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKBootstrapClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.X509BootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.EncryptionUtil; @@ -47,6 +45,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.service.DataValidator; + import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -59,7 +58,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen private DeviceCredentialsDao deviceCredentialsDao; @Autowired - private DeviceService deviceService; + private DataValidator credentialsValidator; @Override public DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId) { @@ -375,46 +374,4 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen deviceCredentialsDao.removeById(tenantId, deviceCredentials.getUuidId()); } - private DataValidator credentialsValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, DeviceCredentials deviceCredentials) { - if (deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId()) != null) { - throw new DeviceCredentialsValidationException("Credentials for this device are already specified!"); - } - if (deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()) != null) { - throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!"); - } - } - - @Override - protected void validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) { - if (deviceCredentialsDao.findById(tenantId, deviceCredentials.getUuidId()) == null) { - throw new DeviceCredentialsValidationException("Unable to update non-existent device credentials!"); - } - DeviceCredentials existingCredentials = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); - if (existingCredentials != null && !existingCredentials.getId().equals(deviceCredentials.getId())) { - throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!"); - } - } - - @Override - protected void validateDataImpl(TenantId tenantId, DeviceCredentials deviceCredentials) { - if (deviceCredentials.getDeviceId() == null) { - throw new DeviceCredentialsValidationException("Device credentials should be assigned to device!"); - } - if (deviceCredentials.getCredentialsType() == null) { - throw new DeviceCredentialsValidationException("Device credentials type should be specified!"); - } - if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) { - throw new DeviceCredentialsValidationException("Device credentials id should be specified!"); - } - Device device = deviceService.findDeviceById(tenantId, deviceCredentials.getDeviceId()); - if (device == null) { - throw new DeviceCredentialsValidationException("Can't assign device credentials to non-existent device!"); - } - } - }; - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index 7bfacb5f78..f33a78ebd1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -15,83 +15,38 @@ */ package org.thingsboard.server.dao.device; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.squareup.wire.Syntax; -import com.squareup.wire.schema.Field; -import com.squareup.wire.schema.Location; -import com.squareup.wire.schema.internal.parser.EnumElement; -import com.squareup.wire.schema.internal.parser.FieldElement; -import com.squareup.wire.schema.internal.parser.MessageElement; -import com.squareup.wire.schema.internal.parser.OneOfElement; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import com.squareup.wire.schema.internal.parser.ProtoParser; -import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.SecurityUtil; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import org.springframework.util.CollectionUtils; -import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.OtaPackage; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; -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.queue.Queue; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.msg.EncryptionUtil; -import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; -import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; -import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TenantDao; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -104,19 +59,6 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId "; private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName "; - private static final Location LOCATION = new Location("", "", -1, -1); - private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; - private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; - private static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema"; - private static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema"; - - private static String invalidSchemaProvidedMessage(String schemaName) { - return "[Transport Configuration] invalid " + schemaName + " provided!"; - } - - @Autowired - private QueueService queueService; - @Autowired private DeviceProfileDao deviceProfileDao; @@ -126,20 +68,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @Autowired private DeviceService deviceService; - @Autowired - private TenantDao tenantDao; - @Autowired private CacheManager cacheManager; - @Autowired - private OtaPackageService otaPackageService; - - @Autowired - private RuleChainService ruleChainService; + @Autowired(required = false) + private QueueService queueService; @Autowired - private DashboardService dashboardService; + private DataValidator deviceProfileValidator; private final Lock findOrCreateLock = new ReentrantLock(); @@ -359,427 +295,6 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D tenantDeviceProfilesRemover.removeEntities(tenantId, tenantId); } - private DataValidator deviceProfileValidator = - new DataValidator<>() { - @Override - protected void validateDataImpl(TenantId tenantId, DeviceProfile deviceProfile) { - if (StringUtils.isEmpty(deviceProfile.getName())) { - throw new DataValidationException("Device profile name should be specified!"); - } - if (deviceProfile.getType() == null) { - throw new DataValidationException("Device profile type should be specified!"); - } - if (deviceProfile.getTransportType() == null) { - throw new DataValidationException("Device profile transport type should be specified!"); - } - if (deviceProfile.getTenantId() == null) { - throw new DataValidationException("Device profile should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(deviceProfile.getTenantId(), deviceProfile.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Device profile is referencing to non-existent tenant!"); - } - } - if (deviceProfile.isDefault()) { - DeviceProfile defaultDeviceProfile = findDefaultDeviceProfile(tenantId); - if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) { - throw new DataValidationException("Another default device profile is present in scope of current tenant!"); - } - } - if (deviceProfile.getDefaultQueueId() != null) { - Queue queue = queueService.findQueueById(tenantId, deviceProfile.getDefaultQueueId()); - if (queue == null) { - throw new DataValidationException("Device profile is referencing to non-existent queue!"); - } - } - if (deviceProfile.getProvisionType() == null) { - deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); - } - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - transportConfiguration.validate(); - if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { - MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; - if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = - (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); - validateProtoSchemas(protoTransportPayloadConfiguration); - validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration); - validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); - } - } else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) { - CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; - CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); - if (coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { - DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); - if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - validateProtoSchemas(protoTransportPayloadConfiguration); - validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration); - validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); - } - } - } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) { - List lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap(); - if (lwM2MBootstrapServersConfigurations != null) { - validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations, - ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable()); - for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { - validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential); - } - } - } - - List profileAlarms = deviceProfile.getProfileData().getAlarms(); - - if (!CollectionUtils.isEmpty(profileAlarms)) { - Set alarmTypes = new HashSet<>(); - for (DeviceProfileAlarm alarm : profileAlarms) { - String alarmType = alarm.getAlarmType(); - if (StringUtils.isEmpty(alarmType)) { - throw new DataValidationException("Alarm rule type should be specified!"); - } - if (!alarmTypes.add(alarmType)) { - throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType)); - } - } - } - - if (deviceProfile.getDefaultRuleChainId() != null) { - RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, deviceProfile.getDefaultRuleChainId()); - if (ruleChain == null) { - throw new DataValidationException("Can't assign non-existent rule chain!"); - } - } - - if (deviceProfile.getDefaultDashboardId() != null) { - DashboardInfo dashboard = dashboardService.findDashboardInfoById(tenantId, deviceProfile.getDefaultDashboardId()); - if (dashboard == null) { - throw new DataValidationException("Can't assign non-existent dashboard!"); - } - } - - if (deviceProfile.getFirmwareId() != null) { - OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getFirmwareId()); - if (firmware == null) { - throw new DataValidationException("Can't assign non-existent firmware!"); - } - if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) { - throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); - } - if (firmware.getData() == null && !firmware.hasUrl()) { - throw new DataValidationException("Can't assign firmware with empty data!"); - } - if (!firmware.getDeviceProfileId().equals(deviceProfile.getId())) { - throw new DataValidationException("Can't assign firmware with different deviceProfile!"); - } - } - - if (deviceProfile.getSoftwareId() != null) { - OtaPackage software = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getSoftwareId()); - if (software == null) { - throw new DataValidationException("Can't assign non-existent software!"); - } - if (!software.getType().equals(OtaPackageType.SOFTWARE)) { - throw new DataValidationException("Can't assign software with type: " + software.getType()); - } - if (software.getData() == null && !software.hasUrl()) { - throw new DataValidationException("Can't assign software with empty data!"); - } - if (!software.getDeviceProfileId().equals(deviceProfile.getId())) { - throw new DataValidationException("Can't assign firmware with different deviceProfile!"); - } - } - } - - @Override - protected void validateUpdate(TenantId tenantId, DeviceProfile deviceProfile) { - DeviceProfile old = deviceProfileDao.findById(deviceProfile.getTenantId(), deviceProfile.getId().getId()); - if (old == null) { - throw new DataValidationException("Can't update non existing device profile!"); - } - boolean profileTypeChanged = !old.getType().equals(deviceProfile.getType()); - boolean transportTypeChanged = !old.getTransportType().equals(deviceProfile.getTransportType()); - if (profileTypeChanged || transportTypeChanged) { - Long profileDeviceCount = deviceDao.countDevicesByDeviceProfileId(deviceProfile.getTenantId(), deviceProfile.getId().getId()); - if (profileDeviceCount > 0) { - String message = null; - if (profileTypeChanged) { - message = "Can't change device profile type because devices referenced it!"; - } else if (transportTypeChanged) { - message = "Can't change device profile transport type because devices referenced it!"; - } - throw new DataValidationException(message); - } - } - } - - private void validateProtoSchemas(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { - try { - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema(), RPC_REQUEST_PROTO_SCHEMA); - validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcResponseProtoSchema(), RPC_RESPONSE_PROTO_SCHEMA); - } catch (Exception exception) { - throw new DataValidationException(exception.getMessage()); - } - } - - private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { - ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); - ProtoFileElement protoFileElement; - try { - protoFileElement = schemaParser.readProtoFile(); - } catch (Exception e) { - throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); - } - checkProtoFileSyntax(schemaName, protoFileElement); - checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); - checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); - checkTypeElements(schemaName, protoFileElement); - } - - private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { - if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { - throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + - " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); - } - } - - private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { - if (!isEmptySettings) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); - } - } - - private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { - List types = protoFileElement.getTypes(); - if (!types.isEmpty()) { - if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); - } else { - checkEnumElements(schemaName, getEnumElements(types)); - checkMessageElements(schemaName, getMessageTypes(types)); - } - } else { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); - } - } - - private void checkFieldElements(String schemaName, List fieldElements) { - if (!fieldElements.isEmpty()) { - boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { - Field.Label label = fieldElement.getLabel(); - return label != null && label.equals(Field.Label.REQUIRED); - }); - if (hasRequiredLabel) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); - } - boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); - if (hasDefaultValue) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); - } - } - } - - private void checkEnumElements(String schemaName, List enumTypes) { - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); - } - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); - } - } - - private void checkMessageElements(String schemaName, List messageElementsList) { - if (!messageElementsList.isEmpty()) { - messageElementsList.forEach(messageElement -> { - checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), - " Message definition groups don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), - " Message definition options don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), - " Message definition extensions don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), - " Message definition reserved elements don't support!"); - checkFieldElements(schemaName, messageElement.getFields()); - List oneOfs = messageElement.getOneOfs(); - if (!oneOfs.isEmpty()) { - oneOfs.forEach(oneOfElement -> { - checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), - " OneOf definition groups don't support!"); - checkFieldElements(schemaName, oneOfElement.getFields()); - }); - } - List nestedTypes = messageElement.getNestedTypes(); - if (!nestedTypes.isEmpty()) { - List nestedEnumTypes = getEnumElements(nestedTypes); - if (!nestedEnumTypes.isEmpty()) { - checkEnumElements(schemaName, nestedEnumTypes); - } - List nestedMessageTypes = getMessageTypes(nestedTypes); - checkMessageElements(schemaName, nestedMessageTypes); - } - }); - } - } - - private List getMessageTypes(List types) { - return types.stream() - .filter(typeElement -> typeElement instanceof MessageElement) - .map(typeElement -> (MessageElement) typeElement) - .collect(Collectors.toList()); - } - - private List getEnumElements(List types) { - return types.stream() - .filter(typeElement -> typeElement instanceof EnumElement) - .map(typeElement -> (EnumElement) typeElement) - .collect(Collectors.toList()); - } - - }; - - private void validateTelemetryDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { - String deviceTelemetryProtoSchema = protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(); - Descriptors.Descriptor telemetryDynamicMessageDescriptor = protoTransportPayloadTypeConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema); - if (telemetryDynamicMessageDescriptor == null) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Failed to get telemetryDynamicMessageDescriptor!"); - } else { - List fields = telemetryDynamicMessageDescriptor.getFields(); - if (CollectionUtils.isEmpty(fields)) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " " + telemetryDynamicMessageDescriptor.getName() + " fields is empty!"); - } else if (fields.size() == 2) { - Descriptors.FieldDescriptor tsFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("ts"); - Descriptors.FieldDescriptor valuesFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("values"); - if (tsFieldDescriptor != null && valuesFieldDescriptor != null) { - if (!Descriptors.FieldDescriptor.Type.MESSAGE.equals(valuesFieldDescriptor.getType())) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'values' has invalid data type. Only message type is supported!"); - } - if (!Descriptors.FieldDescriptor.Type.INT64.equals(tsFieldDescriptor.getType())) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid data type. Only int64 type is supported!"); - } - if (!tsFieldDescriptor.hasOptionalKeyword()) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid label. Field 'ts' should have optional keyword!"); - } - } - } - } - } - - private void validateRpcRequestDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { - DynamicMessage.Builder rpcRequestDynamicMessageBuilder = protoTransportPayloadTypeConfiguration.getRpcRequestDynamicMessageBuilder(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema()); - Descriptors.Descriptor rpcRequestDynamicMessageDescriptor = rpcRequestDynamicMessageBuilder.getDescriptorForType(); - if (rpcRequestDynamicMessageDescriptor == null) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get rpcRequestDynamicMessageDescriptor!"); - } else { - if (CollectionUtils.isEmpty(rpcRequestDynamicMessageDescriptor.getFields()) || rpcRequestDynamicMessageDescriptor.getFields().size() != 3) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " " + rpcRequestDynamicMessageDescriptor.getName() + " message should always contains 3 fields: method, requestId and params!"); - } - Descriptors.FieldDescriptor methodFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("method"); - if (methodFieldDescriptor == null) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: method!"); - } else { - if (!Descriptors.FieldDescriptor.Type.STRING.equals(methodFieldDescriptor.getType())) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid data type. Only string type is supported!"); - } - if (methodFieldDescriptor.isRepeated()) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid label!"); - } - } - Descriptors.FieldDescriptor requestIdFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("requestId"); - if (requestIdFieldDescriptor == null) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: requestId!"); - } else { - if (!Descriptors.FieldDescriptor.Type.INT32.equals(requestIdFieldDescriptor.getType())) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid data type. Only int32 type is supported!"); - } - if (requestIdFieldDescriptor.isRepeated()) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid label!"); - } - } - Descriptors.FieldDescriptor paramsFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("params"); - if (paramsFieldDescriptor == null) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: params!"); - } else { - if (paramsFieldDescriptor.isRepeated()) { - throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'params' has invalid label!"); - } - } - } - } - - private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) { - Set uris = new HashSet<>(); - Set shortServerIds = new HashSet<>(); - for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { - AbstractLwM2MBootstrapServerCredential serverConfig = (AbstractLwM2MBootstrapServerCredential) bootstrapServerCredential; - if (!isBootstrapServerUpdateEnable && serverConfig.isBootstrapServerIs()) { - throw new DeviceCredentialsValidationException("Bootstrap config must not include \"Bootstrap Server\". \"Include Bootstrap Server updates\" is " + isBootstrapServerUpdateEnable + "."); - } - String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server" + " shortServerId: " + serverConfig.getShortServerId() + ":"; - if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) { - throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!"); - } - if (!shortServerIds.add(serverConfig.getShortServerId())) { - throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!"); - } - String uri = serverConfig.getHost() + ":" + serverConfig.getPort(); - if (!uris.add(uri)) { - throw new DeviceCredentialsValidationException(server + " \"Host + port\" value = " + uri + ". This value must be a unique value for all servers!"); - } - Integer port; - if (LwM2MSecurityMode.NO_SEC.equals(serverConfig.getSecurityMode())) { - port = serverConfig.isBootstrapServerIs() ? 5687 : 5685; - } else { - port = serverConfig.isBootstrapServerIs() ? 5688 : 5686; - } - if (serverConfig.getPort() == null || serverConfig.getPort().intValue() != port) { - throw new DeviceCredentialsValidationException(server + " \"Port\" value = " + serverConfig.getPort() + ". This value for security " + serverConfig.getSecurityMode().name() + " must be " + port + "!"); - } - } - } - - private void validateLwm2mServersCredentialOfBootstrapForClient(LwM2MBootstrapServerCredential bootstrapServerConfig) { - String server; - switch (bootstrapServerConfig.getSecurityMode()) { - case NO_SEC: - case PSK: - break; - case RPK: - RPKLwM2MBootstrapServerCredential rpkServerCredentials = (RPKLwM2MBootstrapServerCredential) bootstrapServerConfig; - server = rpkServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; - if (StringUtils.isEmpty(rpkServerCredentials.getServerPublicKey())) { - throw new DeviceCredentialsValidationException(server + " RPK public key must be specified!"); - } - try { - String pubkRpkSever = EncryptionUtil.pubkTrimNewLines(rpkServerCredentials.getServerPublicKey()); - rpkServerCredentials.setServerPublicKey(pubkRpkSever); - SecurityUtil.publicKey.decode(rpkServerCredentials.getDecodedCServerPublicKey()); - } catch (Exception e) { - throw new DeviceCredentialsValidationException(server + " RPK public key must be in standard [RFC7250] and then encoded to Base64 format!"); - } - break; - case X509: - X509LwM2MBootstrapServerCredential x509ServerCredentials = (X509LwM2MBootstrapServerCredential) bootstrapServerConfig; - server = x509ServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; - if (StringUtils.isEmpty(x509ServerCredentials.getServerPublicKey())) { - throw new DeviceCredentialsValidationException(server + " X509 certificate must be specified!"); - } - - try { - String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getServerPublicKey()); - x509ServerCredentials.setServerPublicKey(certServer); - SecurityUtil.certificate.decode(x509ServerCredentials.getDecodedCServerPublicKey()); - } catch (Exception e) { - throw new DeviceCredentialsValidationException(server + " X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!"); - } - break; - } - } - private PaginatedRemover tenantDeviceProfilesRemover = new PaginatedRemover() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index b2a6c10337..a3260ffa17 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -23,18 +23,14 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Caching; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; @@ -42,15 +38,12 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.OtaPackage; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; -import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; @@ -69,23 +62,18 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.cache.EntitiesCacheManager; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import javax.annotation.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -96,7 +84,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE; import static org.thingsboard.server.dao.DaoUtil.toUUIDs; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validatePageLink; @@ -116,12 +103,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Autowired private DeviceDao deviceDao; - @Autowired - private TenantDao tenantDao; - - @Autowired - private CustomerDao customerDao; - @Autowired private DeviceCredentialsService deviceCredentialsService; @@ -129,17 +110,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe private DeviceProfileService deviceProfileService; @Autowired - private CacheManager cacheManager; + private EntitiesCacheManager cacheManager; @Autowired private EventService eventService; @Autowired - @Lazy - private TbTenantProfileCache tenantProfileCache; - - @Autowired - private OtaPackageService otaPackageService; + private DataValidator deviceValidator; @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { @@ -274,8 +251,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { // remove device from cache in case null value cached in the distributed redis. - removeDeviceFromCacheByName(device.getTenantId(), device.getName()); - removeDeviceFromCacheById(device.getTenantId(), device.getId()); + cacheManager.removeDeviceFromCacheByName(device.getTenantId(), device.getName()); + cacheManager.removeDeviceFromCacheById(device.getTenantId(), device.getId()); throw new DataValidationException("Device with such name already exists!"); } else { throw t; @@ -321,8 +298,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe Device device = findDeviceById(tenantId, deviceId); device.setCustomerId(customerId); Device savedDevice = saveDevice(device); - removeDeviceFromCacheByName(tenantId, device.getName()); - removeDeviceFromCacheById(tenantId, device.getId()); + cacheManager.removeDeviceFromCacheByName(tenantId, device.getName()); + cacheManager.removeDeviceFromCacheById(tenantId, device.getId()); return savedDevice; } @@ -331,8 +308,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe Device device = findDeviceById(tenantId, deviceId); device.setCustomerId(null); Device savedDevice = saveDevice(device); - removeDeviceFromCacheByName(tenantId, device.getName()); - removeDeviceFromCacheById(tenantId, device.getId()); + cacheManager.removeDeviceFromCacheByName(tenantId, device.getName()); + cacheManager.removeDeviceFromCacheById(tenantId, device.getId()); return savedDevice; } @@ -362,22 +339,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe deviceDao.removeById(tenantId, deviceId.getId()); - removeDeviceFromCacheByName(tenantId, deviceName); - removeDeviceFromCacheById(tenantId, deviceId); + cacheManager.removeDeviceFromCacheByName(tenantId, deviceName); + cacheManager.removeDeviceFromCacheById(tenantId, deviceId); } - private void removeDeviceFromCacheByName(TenantId tenantId, String name) { - Cache cache = cacheManager.getCache(DEVICE_CACHE); - cache.evict(Arrays.asList(tenantId, name)); - } - private void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) { - if (deviceId == null) { - return; - } - Cache cache = cacheManager.getCache(DEVICE_CACHE); - cache.evict(Arrays.asList(tenantId, deviceId)); - } @Override public PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink) { @@ -590,8 +556,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe // explicitly remove device with previous tenant id from cache // result device object will have different tenant id and will not remove entity from cache - removeDeviceFromCacheByName(oldTenantId, device.getName()); - removeDeviceFromCacheById(oldTenantId, device.getId()); + cacheManager.removeDeviceFromCacheByName(oldTenantId, device.getName()); + cacheManager.removeDeviceFromCacheById(oldTenantId, device.getId()); return savedDevice; } @@ -639,7 +605,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } - removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); // eviction by name is described as annotation @CacheEvict above + cacheManager.removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); // eviction by name is described as annotation @CacheEvict above return savedDevice; } @@ -710,91 +676,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.countByTenantId(tenantId); } - private DataValidator deviceValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, Device device) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxDevices = profileConfiguration.getMaxDevices(); - validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE); - } - - @Override - protected void validateUpdate(TenantId tenantId, Device device) { - Device old = deviceDao.findById(device.getTenantId(), device.getId().getId()); - if (old == null) { - throw new DataValidationException("Can't update non existing device!"); - } - if (!old.getName().equals(device.getName())) { - removeDeviceFromCacheByName(tenantId, old.getName()); - removeDeviceFromCacheById(tenantId, device.getId()); - } - } - - @Override - protected void validateDataImpl(TenantId tenantId, Device device) { - if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) { - throw new DataValidationException("Device name should be specified!"); - } - if (device.getTenantId() == null) { - throw new DataValidationException("Device should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(device.getTenantId(), device.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Device is referencing to non-existent tenant!"); - } - } - if (device.getCustomerId() == null) { - device.setCustomerId(new CustomerId(NULL_UUID)); - } else if (!device.getCustomerId().getId().equals(NULL_UUID)) { - Customer customer = customerDao.findById(device.getTenantId(), device.getCustomerId().getId()); - if (customer == null) { - throw new DataValidationException("Can't assign device to non-existent customer!"); - } - if (!customer.getTenantId().getId().equals(device.getTenantId().getId())) { - throw new DataValidationException("Can't assign device to customer from different tenant!"); - } - } - Optional.ofNullable(device.getDeviceData()) - .flatMap(deviceData -> Optional.ofNullable(deviceData.getTransportConfiguration())) - .ifPresent(DeviceTransportConfiguration::validate); - - if (device.getFirmwareId() != null) { - OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, device.getFirmwareId()); - if (firmware == null) { - throw new DataValidationException("Can't assign non-existent firmware!"); - } - if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) { - throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); - } - if (firmware.getData() == null && !firmware.hasUrl()) { - throw new DataValidationException("Can't assign firmware with empty data!"); - } - if (!firmware.getDeviceProfileId().equals(device.getDeviceProfileId())) { - throw new DataValidationException("Can't assign firmware with different deviceProfile!"); - } - } - - if (device.getSoftwareId() != null) { - OtaPackage software = otaPackageService.findOtaPackageById(tenantId, device.getSoftwareId()); - if (software == null) { - throw new DataValidationException("Can't assign non-existent software!"); - } - if (!software.getType().equals(OtaPackageType.SOFTWARE)) { - throw new DataValidationException("Can't assign software with type: " + software.getType()); - } - if (software.getData() == null && !software.hasUrl()) { - throw new DataValidationException("Can't assign software with empty data!"); - } - if (!software.getDeviceProfileId().equals(device.getDeviceProfileId())) { - throw new DataValidationException("Can't assign firmware with different deviceProfile!"); - } - } - } - }; - private PaginatedRemover tenantDevicesRemover = new PaginatedRemover() { 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 aa96850a67..f9e94af613 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,6 +15,7 @@ */ package org.thingsboard.server.dao.edge; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -23,7 +24,6 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @Service @@ -33,10 +33,13 @@ public class BaseEdgeEventService implements EdgeEventService { @Autowired private EdgeEventDao edgeEventDao; + @Autowired + private DataValidator edgeEventValidator; + @Override - public EdgeEvent save(EdgeEvent edgeEvent) { + public ListenableFuture saveAsync(EdgeEvent edgeEvent) { edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); - return edgeEventDao.save(edgeEvent); + return edgeEventDao.saveAsync(edgeEvent); } @Override @@ -48,17 +51,4 @@ public class BaseEdgeEventService implements EdgeEventService { public void cleanupEvents(long ttl) { edgeEventDao.cleanupEvents(ttl); } - - private DataValidator edgeEventValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId tenantId, EdgeEvent edgeEvent) { - if (edgeEvent.getEdgeId() == null) { - throw new DataValidationException("Edge id should be specified!"); - } - if (edgeEvent.getAction() == null) { - throw new DataValidationException("Edge Event action should be specified!"); - } - } - }; } 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 5ea81eb1f9..7a43d6c065 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 @@ -35,7 +35,7 @@ public interface EdgeEventDao extends Dao { * @param edgeEvent the event object * @return saved edge event object future */ - EdgeEvent save(EdgeEvent edgeEvent); + ListenableFuture saveAsync(EdgeEvent edgeEvent); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 3cdbcf7bc9..80d3a1bb03 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -25,16 +25,11 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeInfo; @@ -52,7 +47,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; -import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.cache.EntitiesCacheManager; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; @@ -60,12 +55,10 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.user.UserService; import javax.annotation.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; @@ -74,7 +67,6 @@ import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.EDGE_CACHE; import static org.thingsboard.server.dao.DaoUtil.toUUIDs; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validatePageLink; @@ -95,17 +87,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic @Autowired private EdgeDao edgeDao; - @Autowired - private TenantDao tenantDao; - - @Autowired - private CustomerDao customerDao; - @Autowired private UserService userService; @Autowired - private CacheManager cacheManager; + private EntitiesCacheManager cacheManager; @Autowired private RuleChainService ruleChainService; @@ -113,6 +99,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic @Autowired private RelationService relationService; + @Autowired + private DataValidator edgeValidator; + @Override public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) { log.trace("Executing findEdgeById [{}]", edgeId); @@ -152,11 +141,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic @CacheEvict(cacheNames = EDGE_CACHE, key = "{#edge.tenantId, #edge.name}") @Override - public Edge saveEdge(Edge edge, boolean doValidate) { + public Edge saveEdge(Edge edge) { log.trace("Executing saveEdge [{}]", edge); - if (doValidate) { - edgeValidator.validate(edge, Edge::getTenantId); - } + edgeValidator.validate(edge, Edge::getTenantId); try { return edgeDao.save(edge.getTenantId(), edge); } catch (Exception t) { @@ -175,7 +162,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic log.trace("[{}] Executing assignEdgeToCustomer [{}][{}]", tenantId, edgeId, customerId); Edge edge = findEdgeById(tenantId, edgeId); edge.setCustomerId(customerId); - return saveEdge(edge, true); + return saveEdge(edge); } @Override @@ -183,7 +170,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic log.trace("[{}] Executing unassignEdgeFromCustomer [{}]", tenantId, edgeId); Edge edge = findEdgeById(tenantId, edgeId); edge.setCustomerId(null); - return saveEdge(edge, true); + return saveEdge(edge); } @Override @@ -195,16 +182,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic deleteEntityRelations(tenantId, edgeId); - removeEdgeFromCacheByName(edge.getTenantId(), edge.getName()); + cacheManager.removeEdgeFromCacheByName(edge.getTenantId(), edge.getName()); edgeDao.removeById(tenantId, edgeId.getId()); } - private void removeEdgeFromCacheByName(TenantId tenantId, String name) { - Cache cache = cacheManager.getCache(EDGE_CACHE); - cache.evict(Arrays.asList(tenantId, name)); - } - @Override public PageData findEdgesByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findEdgesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); @@ -330,7 +312,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic @Nullable @Override public List apply(@Nullable List edgeList) { - return edgeList == null ? Collections.emptyList() : edgeList.stream().filter(edge -> query.getEdgeTypes().contains(edge.getType())).collect(Collectors.toList()); + return edgeList == null ? + Collections.emptyList() : + edgeList.stream().filter(edge -> query.getEdgeTypes().contains(edge.getType())).collect(Collectors.toList()); } }, MoreExecutors.directExecutor()); @@ -375,57 +359,6 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic return edgeDao.findEdgesByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), entityId.getEntityType(), pageLink); } - private DataValidator edgeValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, Edge edge) { - } - - @Override - protected void validateUpdate(TenantId tenantId, Edge edge) { - Edge old = edgeDao.findById(edge.getTenantId(), edge.getId().getId()); - if (!old.getName().equals(edge.getName())) { - removeEdgeFromCacheByName(tenantId, old.getName()); - } - } - - @Override - protected void validateDataImpl(TenantId tenantId, Edge edge) { - if (StringUtils.isEmpty(edge.getType())) { - throw new DataValidationException("Edge type should be specified!"); - } - if (StringUtils.isEmpty(edge.getName())) { - throw new DataValidationException("Edge name should be specified!"); - } - if (StringUtils.isEmpty(edge.getSecret())) { - throw new DataValidationException("Edge secret should be specified!"); - } - if (StringUtils.isEmpty(edge.getRoutingKey())) { - throw new DataValidationException("Edge routing key should be specified!"); - } - if (edge.getTenantId() == null) { - throw new DataValidationException("Edge should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(edge.getTenantId(), edge.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Edge is referencing to non-existent tenant!"); - } - } - if (edge.getCustomerId() == null) { - edge.setCustomerId(new CustomerId(NULL_UUID)); - } else if (!edge.getCustomerId().getId().equals(NULL_UUID)) { - Customer customer = customerDao.findById(edge.getTenantId(), edge.getCustomerId().getId()); - if (customer == null) { - throw new DataValidationException("Can't assign edge to non-existent customer!"); - } - if (!customer.getTenantId().getId().equals(edge.getTenantId().getId())) { - throw new DataValidationException("Can't assign edge to customer from different tenant!"); - } - } - } - }; - private PaginatedRemover tenantEdgesRemover = new PaginatedRemover() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index 624c5e5ef7..5705c463cb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -21,20 +21,15 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; -import org.springframework.cache.annotation.Caching; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; @@ -47,12 +42,10 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import org.thingsboard.server.dao.tenant.TenantDao; import javax.annotation.Nullable; import java.util.ArrayList; @@ -65,7 +58,6 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validateString; @@ -87,38 +79,36 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti private EntityViewDao entityViewDao; @Autowired - private TenantDao tenantDao; + private CacheManager cacheManager; @Autowired - private CustomerDao customerDao; + private DataValidator entityViewValidator; - @Autowired - private CacheManager cacheManager; - - @Caching(evict = { - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"), - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.name}"), - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")}) @Override public EntityView saveEntityView(EntityView entityView) { log.trace("Executing save entity view [{}]", entityView); entityViewValidator.validate(entityView, EntityView::getTenantId); - EntityView savedEntityView = entityViewDao.save(entityView.getTenantId(), entityView); - return savedEntityView; + + if (entityView.getId() != null) { + EntityView oldEntityView = entityViewDao.findById(entityView.getTenantId(), entityView.getUuidId()); + evictCache(oldEntityView); + } + + return entityViewDao.save(entityView.getTenantId(), entityView); } - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, CustomerId customerId) { EntityView entityView = findEntityViewById(tenantId, entityViewId); + evictCache(entityView); entityView.setCustomerId(customerId); return saveEntityView(entityView); } - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId) { EntityView entityView = findEntityViewById(tenantId, entityViewId); + evictCache(entityView); entityView.setCustomerId(null); return saveEntityView(entityView); } @@ -302,15 +292,13 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti } } - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public void deleteEntityView(TenantId tenantId, EntityViewId entityViewId) { log.trace("Executing deleteEntityView [{}]", entityViewId); validateId(entityViewId, INCORRECT_ENTITY_VIEW_ID + entityViewId); deleteEntityRelations(tenantId, entityViewId); EntityView entityView = entityViewDao.findById(tenantId, entityViewId.getId()); - cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId())); - cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getName())); + evictCache(entityView); entityViewDao.removeById(tenantId, entityViewId.getId()); } @@ -333,7 +321,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti }, MoreExecutors.directExecutor()); } - @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @Override public EntityView assignEntityViewToEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId) { EntityView entityView = findEntityViewById(tenantId, entityViewId); @@ -400,57 +387,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti return entityViewDao.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink); } - private DataValidator entityViewValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, EntityView entityView) { - entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) - .ifPresent(e -> { - throw new DataValidationException("Entity view with such name already exists!"); - }); - } - - @Override - protected void validateUpdate(TenantId tenantId, EntityView entityView) { - entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) - .ifPresent(e -> { - if (!e.getUuidId().equals(entityView.getUuidId())) { - throw new DataValidationException("Entity view with such name already exists!"); - } - }); - } - - @Override - protected void validateDataImpl(TenantId tenantId, EntityView entityView) { - if (StringUtils.isEmpty(entityView.getType())) { - throw new DataValidationException("Entity View type should be specified!"); - } - if (StringUtils.isEmpty(entityView.getName())) { - throw new DataValidationException("Entity view name should be specified!"); - } - if (entityView.getTenantId() == null) { - throw new DataValidationException("Entity view should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(tenantId, entityView.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Entity view is referencing to non-existent tenant!"); - } - } - if (entityView.getCustomerId() == null) { - entityView.setCustomerId(new CustomerId(NULL_UUID)); - } else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) { - Customer customer = customerDao.findById(tenantId, entityView.getCustomerId().getId()); - if (customer == null) { - throw new DataValidationException("Can't assign entity view to non-existent customer!"); - } - if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) { - throw new DataValidationException("Can't assign entity view to customer from different tenant!"); - } - } - } - }; - private PaginatedRemover tenantEntityViewRemover = new PaginatedRemover() { @Override protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { @@ -474,4 +410,11 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti unassignEntityViewFromCustomer(tenantId, new EntityViewId(entity.getUuidId())); } }; + + private void evictCache(EntityView entityView) { + Cache cache = cacheManager.getCache(ENTITY_VIEW_CACHE); + cache.evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId())); + cache.evict(Arrays.asList(entityView.getTenantId(), entityView.getName())); + cache.evict(Arrays.asList(entityView.getId())); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index a94b71405a..3937bbd9c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -46,29 +46,16 @@ public class BaseEventService implements EventService { @Autowired public EventDao eventDao; - @Override - public Event save(Event event) { - eventValidator.validate(event, Event::getTenantId); - return eventDao.save(event.getTenantId(), event); - } + @Autowired + private DataValidator eventValidator; @Override - public ListenableFuture saveAsync(Event event) { + public ListenableFuture saveAsync(Event event) { eventValidator.validate(event, Event::getTenantId); checkAndTruncateDebugEvent(event); return eventDao.saveAsync(event); } - @Override - public Optional saveIfNotExists(Event event) { - eventValidator.validate(event, Event::getTenantId); - if (StringUtils.isEmpty(event.getUid())) { - throw new DataValidationException("Event uid should be specified!."); - } - checkAndTruncateDebugEvent(event); - return eventDao.saveIfNotExists(event); - } - private void checkAndTruncateDebugEvent(Event event) { if (event.getType().startsWith("DEBUG") && event.getBody() != null && event.getBody().has("data")) { String dataStr = event.getBody().get("data").asText(); @@ -141,23 +128,7 @@ public class BaseEventService implements EventService { } @Override - public void cleanupEvents(long ttl, long debugTtl) { - eventDao.cleanupEvents(ttl, debugTtl); + public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) { + eventDao.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs); } - - private DataValidator eventValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId tenantId, Event event) { - if (event.getEntityId() == null) { - throw new DataValidationException("Entity id should be specified!."); - } - if (StringUtils.isEmpty(event.getType())) { - throw new DataValidationException("Event type should be specified!."); - } - if (event.getBody() == null) { - throw new DataValidationException("Event body should be specified!."); - } - } - }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java index bb143fd248..f3574fb419 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java @@ -33,29 +33,13 @@ import java.util.UUID; */ public interface EventDao extends Dao { - /** - * Save or update event object - * - * @param event the event object - * @return saved event object - */ - Event save(TenantId tenantId, Event event); - /** * Save or update event object async * * @param event the event object * @return saved event object future */ - ListenableFuture saveAsync(Event event); - - /** - * Save event object if it is not yet saved - * - * @param event the event object - * @return saved event object - */ - Optional saveIfNotExists(Event event); + ListenableFuture saveAsync(Event event); /** * Find event by tenantId, entityId and eventUid. @@ -104,8 +88,10 @@ public interface EventDao extends Dao { /** * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. - * @param otherEventsTtl the ttl for events in seconds - * @param debugEventsTtl the ttl for debug events in seconds + * @param regularEventStartTs the start time of the interval to use to delete non debug events + * @param regularEventEndTs the end time of the interval to use to delete non debug events + * @param debugEventStartTs the start time of the interval to use to delete debug events + * @param debugEventEndTs the end time of the interval to use to delete debug events */ - void cleanupEvents(long otherEventsTtl, long debugEventsTtl); + void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java index a3d6099c5d..d35d7e241c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java @@ -15,15 +15,13 @@ */ package org.thingsboard.server.dao.oauth2; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @@ -36,12 +34,14 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Slf4j @Service +@AllArgsConstructor public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService { - public static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId "; - public static final String INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID = "Incorrect clientRegistrationProviderId "; - @Autowired - private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao; + private static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId "; + private static final String INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID = "Incorrect clientRegistrationProviderId "; + + private final OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao; + private final DataValidator clientRegistrationTemplateValidator; @Override public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { @@ -87,32 +87,4 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId); clientRegistrationTemplateDao.removeById(TenantId.SYS_TENANT_ID, templateId.getId()); } - - private final DataValidator clientRegistrationTemplateValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { - } - - @Override - protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { - } - - @Override - protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { - if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) { - throw new DataValidationException("Provider ID should be specified!"); - } - if (clientRegistrationTemplate.getMapperConfig() == null) { - throw new DataValidationException("Mapper config should be specified!"); - } - if (clientRegistrationTemplate.getMapperConfig().getType() == null) { - throw new DataValidationException("Mapper type should be specified!"); - } - if (clientRegistrationTemplate.getMapperConfig().getBasic() == null) { - throw new DataValidationException("Basic mapper config should be specified!"); - } - } - }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java index 838e79ad70..ecf8d68267 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java @@ -21,18 +21,14 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.ota.OtaPackageDataCache; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; @@ -40,22 +36,16 @@ import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; 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.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import java.nio.ByteBuffer; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.Optional; import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE; -import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validatePageLink; @@ -66,16 +56,12 @@ public class BaseOtaPackageService implements OtaPackageService { public static final String INCORRECT_OTA_PACKAGE_ID = "Incorrect otaPackageId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - private final TenantDao tenantDao; - private final DeviceProfileDao deviceProfileDao; private final OtaPackageDao otaPackageDao; private final OtaPackageInfoDao otaPackageInfoDao; private final CacheManager cacheManager; private final OtaPackageDataCache otaPackageDataCache; - - @Autowired - @Lazy - private TbTenantProfileCache tenantProfileCache; + private final DataValidator otaPackageInfoValidator; + private final DataValidator otaPackageValidator; @Override public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl) { @@ -230,168 +216,6 @@ public class BaseOtaPackageService implements OtaPackageService { tenantOtaPackageRemover.removeEntities(tenantId, tenantId); } - private DataValidator otaPackageInfoValidator = new DataValidator<>() { - - @Override - protected void validateDataImpl(TenantId tenantId, OtaPackageInfo otaPackageInfo) { - validateImpl(otaPackageInfo); - } - - @Override - protected void validateUpdate(TenantId tenantId, OtaPackageInfo otaPackage) { - OtaPackageInfo otaPackageOld = otaPackageInfoDao.findById(tenantId, otaPackage.getUuidId()); - BaseOtaPackageService.validateUpdate(otaPackage, otaPackageOld); - } - }; - - private DataValidator otaPackageValidator = new DataValidator<>() { - - @Override - protected void validateCreate(TenantId tenantId, OtaPackage otaPackage) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes(); - validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE); - } - - @Override - protected void validateDataImpl(TenantId tenantId, OtaPackage otaPackage) { - validateImpl(otaPackage); - - if (!otaPackage.hasUrl()) { - if (StringUtils.isEmpty(otaPackage.getFileName())) { - throw new DataValidationException("OtaPackage file name should be specified!"); - } - - if (StringUtils.isEmpty(otaPackage.getContentType())) { - throw new DataValidationException("OtaPackage content type should be specified!"); - } - - if (otaPackage.getChecksumAlgorithm() == null) { - throw new DataValidationException("OtaPackage checksum algorithm should be specified!"); - } - if (StringUtils.isEmpty(otaPackage.getChecksum())) { - throw new DataValidationException("OtaPackage checksum should be specified!"); - } - - String currentChecksum; - - currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData()); - - if (!currentChecksum.equals(otaPackage.getChecksum())) { - throw new DataValidationException("Wrong otaPackage file!"); - } - } else { - if(otaPackage.getData() != null) { - throw new DataValidationException("File can't be saved if URL present!"); - } - } - } - - @Override - protected void validateUpdate(TenantId tenantId, OtaPackage otaPackage) { - OtaPackage otaPackageOld = otaPackageDao.findById(tenantId, otaPackage.getUuidId()); - - BaseOtaPackageService.validateUpdate(otaPackage, otaPackageOld); - - if (otaPackageOld.getData() != null && !otaPackageOld.getData().equals(otaPackage.getData())) { - throw new DataValidationException("Updating otaPackage data is prohibited!"); - } - - if (otaPackageOld.getData() == null && otaPackage.getData() != null) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes(); - validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE); - } - } - }; - - private static void validateUpdate(OtaPackageInfo otaPackage, OtaPackageInfo otaPackageOld) { - if (!otaPackageOld.getType().equals(otaPackage.getType())) { - throw new DataValidationException("Updating type is prohibited!"); - } - - if (!otaPackageOld.getTitle().equals(otaPackage.getTitle())) { - throw new DataValidationException("Updating otaPackage title is prohibited!"); - } - - if (!otaPackageOld.getVersion().equals(otaPackage.getVersion())) { - throw new DataValidationException("Updating otaPackage version is prohibited!"); - } - - if (!Objects.equals(otaPackage.getTag(), otaPackageOld.getTag())) { - throw new DataValidationException("Updating otaPackage tag is prohibited!"); - } - - if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) { - throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!"); - } - - if (otaPackageOld.getFileName() != null && !otaPackageOld.getFileName().equals(otaPackage.getFileName())) { - throw new DataValidationException("Updating otaPackage file name is prohibited!"); - } - - if (otaPackageOld.getContentType() != null && !otaPackageOld.getContentType().equals(otaPackage.getContentType())) { - throw new DataValidationException("Updating otaPackage content type is prohibited!"); - } - - if (otaPackageOld.getChecksumAlgorithm() != null && !otaPackageOld.getChecksumAlgorithm().equals(otaPackage.getChecksumAlgorithm())) { - throw new DataValidationException("Updating otaPackage content type is prohibited!"); - } - - if (otaPackageOld.getChecksum() != null && !otaPackageOld.getChecksum().equals(otaPackage.getChecksum())) { - throw new DataValidationException("Updating otaPackage content type is prohibited!"); - } - - if (otaPackageOld.getDataSize() != null && !otaPackageOld.getDataSize().equals(otaPackage.getDataSize())) { - throw new DataValidationException("Updating otaPackage data size is prohibited!"); - } - - if(otaPackageOld.getUrl() != null && !otaPackageOld.getUrl().equals(otaPackage.getUrl())) { - throw new DataValidationException("Updating otaPackage URL is prohibited!"); - } - } - - private void validateImpl(OtaPackageInfo otaPackageInfo) { - if (otaPackageInfo.getTenantId() == null) { - throw new DataValidationException("OtaPackage should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("OtaPackage is referencing to non-existent tenant!"); - } - } - - if (otaPackageInfo.getDeviceProfileId() != null) { - DeviceProfile deviceProfile = deviceProfileDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getDeviceProfileId().getId()); - if (deviceProfile == null) { - throw new DataValidationException("OtaPackage is referencing to non-existent device profile!"); - } - } - - if (otaPackageInfo.getType() == null) { - throw new DataValidationException("Type should be specified!"); - } - - if (StringUtils.isEmpty(otaPackageInfo.getTitle())) { - throw new DataValidationException("OtaPackage title should be specified!"); - } - - if (StringUtils.isEmpty(otaPackageInfo.getVersion())) { - throw new DataValidationException("OtaPackage version should be specified!"); - } - - if(otaPackageInfo.getTitle().length() > 255) { - throw new DataValidationException("The length of title should be equal or shorter than 255"); - } - - if(otaPackageInfo.getVersion().length() > 255) { - throw new DataValidationException("The length of version should be equal or shorter than 255"); - } - - } - private PaginatedRemover tenantOtaPackageRemover = new PaginatedRemover<>() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index c515e95f73..dd13f5b200 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -16,51 +16,37 @@ package org.thingsboard.server.dao.resource; import com.google.common.util.concurrent.ListenableFuture; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TbResourceId; 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.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import java.util.List; import java.util.Optional; -import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE; import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID; import static org.thingsboard.server.dao.service.Validator.validateId; @Service @Slf4j +@AllArgsConstructor public class BaseResourceService implements ResourceService { public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId "; private final TbResourceDao resourceDao; private final TbResourceInfoDao resourceInfoDao; - private final TenantDao tenantDao; - private final TbTenantProfileCache tenantProfileCache; - - public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao, @Lazy TbTenantProfileCache tenantProfileCache) { - this.resourceDao = resourceDao; - this.resourceInfoDao = resourceInfoDao; - this.tenantDao = tenantDao; - this.tenantProfileCache = tenantProfileCache; - } + private final DataValidator resourceValidator; @Override public TbResource saveResource(TbResource resource) { @@ -153,45 +139,7 @@ public class BaseResourceService implements ResourceService { return resourceDao.sumDataSizeByTenantId(tenantId); } - private DataValidator resourceValidator = new DataValidator<>() { - - @Override - protected void validateCreate(TenantId tenantId, TbResource resource) { - if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId) ) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes(); - validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length(), TB_RESOURCE); - } - } - - @Override - protected void validateDataImpl(TenantId tenantId, TbResource resource) { - if (StringUtils.isEmpty(resource.getTitle())) { - throw new DataValidationException("Resource title should be specified!"); - } - if (resource.getResourceType() == null) { - throw new DataValidationException("Resource type should be specified!"); - } - if (StringUtils.isEmpty(resource.getFileName())) { - throw new DataValidationException("Resource file name should be specified!"); - } - if (StringUtils.isEmpty(resource.getResourceKey())) { - throw new DataValidationException("Resource key should be specified!"); - } - if (resource.getTenantId() == null) { - resource.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID)); - } - if (!resource.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { - Tenant tenant = tenantDao.findById(tenantId, resource.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Resource is referencing to non-existent tenant!"); - } - } - } - }; - - private PaginatedRemover tenantResourcesRemover = + private final PaginatedRemover tenantResourcesRemover = new PaginatedRemover<>() { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index b522cb8bf0..097779f33e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -21,16 +21,14 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; @@ -51,15 +49,12 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainUpdateResult; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; import java.util.Collection; @@ -94,11 +89,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC private RuleNodeDao ruleNodeDao; @Autowired - private TenantDao tenantDao; - - @Autowired - @Lazy - private TbTenantProfileCache tenantProfileCache; + private DataValidator ruleChainValidator; @Override @Transactional @@ -209,11 +200,37 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } if (ruleChainMetaData.getRuleChainConnections() != null) { for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { + RuleChainId targetRuleChainId = nodeToRuleChainConnection.getTargetRuleChainId(); + RuleChain targetRuleChain = findRuleChainById(TenantId.SYS_TENANT_ID, targetRuleChainId); + RuleNode targetNode = new RuleNode(); + targetNode.setName(targetRuleChain != null ? targetRuleChain.getName() : "Rule Chain Input"); + targetNode.setRuleChainId(ruleChain.getId()); + targetNode.setType("org.thingsboard.rule.engine.flow.TbRuleChainInputNode"); + var configuration = JacksonUtil.newObjectNode(); + configuration.put("ruleChainId", targetRuleChainId.getId().toString()); + targetNode.setConfiguration(configuration); + ObjectNode layout = (ObjectNode) nodeToRuleChainConnection.getAdditionalInfo(); + layout.remove("description"); + layout.remove("ruleChainNodeId"); + targetNode.setAdditionalInfo(layout); + targetNode.setDebugMode(false); + targetNode = ruleNodeDao.save(tenantId, targetNode); + + EntityRelation sourceRuleChainToRuleNode = new EntityRelation(); + sourceRuleChainToRuleNode.setFrom(ruleChain.getId()); + sourceRuleChainToRuleNode.setTo(targetNode.getId()); + sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE); + sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN); + relationService.saveRelation(tenantId, sourceRuleChainToRuleNode); + + EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation(); EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); - EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); - String type = nodeToRuleChainConnection.getType(); - createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); - } + sourceRuleNodeToTargetRuleNode.setFrom(from); + sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId()); + sourceRuleNodeToTargetRuleNode.setType(nodeToRuleChainConnection.getType()); + sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE); + relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode); + } } } @@ -273,8 +290,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC int toIndex = ruleNodeIndexMap.get(toNodeId); ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { - RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId()); - ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type, nodeRelation.getAdditionalInfo()); + log.warn("[{}][{}] Unsupported node relation: {}", tenantId, ruleChainId, nodeRelation.getTo()); } } } @@ -519,23 +535,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } } - private List findAllTenantRuleChains(TenantId tenantId, RuleChainType type) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - return findAllTenantRuleChainsRecursive(tenantId, new ArrayList<>(), type, pageLink); - } - - private List findAllTenantRuleChainsRecursive(TenantId tenantId, List accumulator, RuleChainType type, PageLink pageLink) { - PageData persistentRuleChainData = findTenantRuleChainsByType(tenantId, type, pageLink); - List ruleChains = persistentRuleChainData.getData(); - if (!CollectionUtils.isEmpty(ruleChains)) { - accumulator.addAll(ruleChains); - } - if (persistentRuleChainData.hasNext()) { - return findAllTenantRuleChainsRecursive(tenantId, accumulator, type, pageLink.nextPageLink()); - } - return accumulator; - } - private void setNewRuleChainId(RuleChain ruleChain, List metadata, RuleChainId oldRuleChainId, RuleChainId newRuleChainId) { ruleChain.setId(newRuleChainId); for (RuleChainMetaData metaData : metadata) { @@ -705,46 +704,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ruleNodeDao.removeById(tenantId, entityId.getId()); } - private final DataValidator ruleChainValidator = - new DataValidator<>() { - @Override - protected void validateCreate(TenantId tenantId, RuleChain data) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxRuleChains = profileConfiguration.getMaxRuleChains(); - validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN); - } - - @Override - protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) { - if (StringUtils.isEmpty(ruleChain.getName())) { - throw new DataValidationException("Rule chain name should be specified!"); - } - if (ruleChain.getType() == null) { - ruleChain.setType(RuleChainType.CORE); - } - if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) { - throw new DataValidationException("Rule chain should be assigned to tenant!"); - } - Tenant tenant = tenantDao.findById(tenantId, ruleChain.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Rule chain is referencing to non-existent tenant!"); - } - if (ruleChain.isRoot() && RuleChainType.CORE.equals(ruleChain.getType())) { - RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); - if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) { - throw new DataValidationException("Another root rule chain is present in scope of current tenant!"); - } - } - if (ruleChain.isRoot() && RuleChainType.EDGE.equals(ruleChain.getType())) { - RuleChain edgeTemplateRootRuleChain = getEdgeTemplateRootRuleChain(ruleChain.getTenantId()); - if (edgeTemplateRootRuleChain != null && !edgeTemplateRootRuleChain.getId().equals(ruleChain.getId())) { - throw new DataValidationException("Another edge template root rule chain is present in scope of current tenant!"); - } - } - } - }; - private final PaginatedRemover tenantRuleChainsRemover = new PaginatedRemover<>() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java new file mode 100644 index 0000000000..c1485cfb1a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.settings.AdminSettingsService; + +@Component +@AllArgsConstructor +public class AdminSettingsDataValidator extends DataValidator { + + private final AdminSettingsService adminSettingsService; + + @Override + protected void validateCreate(TenantId tenantId, AdminSettings adminSettings) { + AdminSettings existentAdminSettingsWithKey = adminSettingsService.findAdminSettingsByKey(tenantId, adminSettings.getKey()); + if (existentAdminSettingsWithKey != null) { + throw new DataValidationException("Admin settings with such name already exists!"); + } + } + + @Override + protected void validateUpdate(TenantId tenantId, AdminSettings adminSettings) { + AdminSettings existentAdminSettings = adminSettingsService.findAdminSettingsById(tenantId, adminSettings.getId()); + if (existentAdminSettings != null) { + if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) { + throw new DataValidationException("Changing key of admin settings entry is prohibited!"); + } + } + } + + + @Override + protected void validateDataImpl(TenantId tenantId, AdminSettings adminSettings) { + if (StringUtils.isEmpty(adminSettings.getKey())) { + throw new DataValidationException("Key should be specified!"); + } + if (adminSettings.getJsonValue() == null) { + throw new DataValidationException("Json value should be specified!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java new file mode 100644 index 0000000000..5bbe636dc7 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +@Component +@AllArgsConstructor +public class AlarmDataValidator extends DataValidator { + + private final TenantDao tenantDao; + + @Override + protected void validateDataImpl(TenantId tenantId, Alarm alarm) { + if (StringUtils.isEmpty(alarm.getType())) { + throw new DataValidationException("Alarm type should be specified!"); + } + if (alarm.getOriginator() == null) { + throw new DataValidationException("Alarm originator should be specified!"); + } + if (alarm.getSeverity() == null) { + throw new DataValidationException("Alarm severity should be specified!"); + } + if (alarm.getStatus() == null) { + throw new DataValidationException("Alarm status should be specified!"); + } + if (alarm.getTenantId() == null) { + throw new DataValidationException("Alarm should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(alarm.getTenantId(), alarm.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Alarm is referencing to non-existent tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java new file mode 100644 index 0000000000..a89acb9631 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +@Component +@AllArgsConstructor +public class ApiUsageDataValidator extends DataValidator { + + private final TenantDao tenantDao; + + @Override + protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) { + if (apiUsageState.getTenantId() == null) { + throw new DataValidationException("ApiUsageState should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId()); + if (tenant == null && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) { + throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!"); + } + } + if (apiUsageState.getEntityId() == null) { + throw new DataValidationException("UsageRecord should be assigned to entity!"); + } else if (apiUsageState.getEntityId().getEntityType() != EntityType.TENANT && apiUsageState.getEntityId().getEntityType() != EntityType.CUSTOMER) { + throw new DataValidationException("Only Tenant and Customer Usage Records are supported!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java new file mode 100644 index 0000000000..57d39ee781 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java @@ -0,0 +1,108 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.asset.AssetDao; +import org.thingsboard.server.dao.asset.BaseAssetService; +import org.thingsboard.server.dao.cache.EntitiesCacheManager; +import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; + +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; + +@Component +public class AssetDataValidator extends DataValidator { + + @Autowired + private AssetDao assetDao; + + @Autowired + private TenantDao tenantDao; + + @Autowired + private CustomerDao customerDao; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Autowired + private EntitiesCacheManager cacheManager; + + @Override + protected void validateCreate(TenantId tenantId, Asset asset) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) { + long maxAssets = profileConfiguration.getMaxAssets(); + validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET); + } + } + + @Override + protected void validateUpdate(TenantId tenantId, Asset asset) { + Asset old = assetDao.findById(asset.getTenantId(), asset.getId().getId()); + if (old == null) { + throw new DataValidationException("Can't update non existing asset!"); + } + if (!old.getName().equals(asset.getName())) { + cacheManager.removeAssetFromCacheByName(tenantId, old.getName()); + } + } + + @Override + protected void validateDataImpl(TenantId tenantId, Asset asset) { + if (StringUtils.isEmpty(asset.getType())) { + throw new DataValidationException("Asset type should be specified!"); + } + if (StringUtils.isEmpty(asset.getName())) { + throw new DataValidationException("Asset name should be specified!"); + } + if (asset.getTenantId() == null) { + throw new DataValidationException("Asset should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(tenantId, asset.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Asset is referencing to non-existent tenant!"); + } + } + if (asset.getCustomerId() == null) { + asset.setCustomerId(new CustomerId(NULL_UUID)); + } else if (!asset.getCustomerId().getId().equals(NULL_UUID)) { + Customer customer = customerDao.findById(tenantId, asset.getCustomerId().getId()); + if (customer == null) { + throw new DataValidationException("Can't assign asset to non-existent customer!"); + } + if (!customer.getTenantId().equals(asset.getTenantId())) { + throw new DataValidationException("Can't assign asset to customer from different tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java new file mode 100644 index 0000000000..619e229595 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +public class AuditLogDataValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, AuditLog auditLog) { + if (auditLog.getEntityId() == null) { + throw new DataValidationException("Entity Id should be specified!"); + } + if (auditLog.getTenantId() == null) { + throw new DataValidationException("Tenant Id should be specified!"); + } + if (auditLog.getUserId() == null) { + throw new DataValidationException("User Id should be specified!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java new file mode 100644 index 0000000000..8184ff8e8e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java @@ -0,0 +1,123 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.dao.device.DeviceProfileDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import java.util.Objects; + +public abstract class BaseOtaPackageDataValidator> extends DataValidator { + + @Autowired + private TenantDao tenantDao; + + @Autowired + private DeviceProfileDao deviceProfileDao; + + protected void validateImpl(OtaPackageInfo otaPackageInfo) { + if (otaPackageInfo.getTenantId() == null) { + throw new DataValidationException("OtaPackage should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("OtaPackage is referencing to non-existent tenant!"); + } + } + + if (otaPackageInfo.getDeviceProfileId() != null) { + DeviceProfile deviceProfile = deviceProfileDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getDeviceProfileId().getId()); + if (deviceProfile == null) { + throw new DataValidationException("OtaPackage is referencing to non-existent device profile!"); + } + } + + if (otaPackageInfo.getType() == null) { + throw new DataValidationException("Type should be specified!"); + } + + if (StringUtils.isEmpty(otaPackageInfo.getTitle())) { + throw new DataValidationException("OtaPackage title should be specified!"); + } + + if (StringUtils.isEmpty(otaPackageInfo.getVersion())) { + throw new DataValidationException("OtaPackage version should be specified!"); + } + + if (otaPackageInfo.getTitle().length() > 255) { + throw new DataValidationException("The length of title should be equal or shorter than 255"); + } + + if (otaPackageInfo.getVersion().length() > 255) { + throw new DataValidationException("The length of version should be equal or shorter than 255"); + } + } + + protected void validateUpdate(OtaPackageInfo otaPackage, OtaPackageInfo otaPackageOld) { + if (!otaPackageOld.getType().equals(otaPackage.getType())) { + throw new DataValidationException("Updating type is prohibited!"); + } + + if (!otaPackageOld.getTitle().equals(otaPackage.getTitle())) { + throw new DataValidationException("Updating otaPackage title is prohibited!"); + } + + if (!otaPackageOld.getVersion().equals(otaPackage.getVersion())) { + throw new DataValidationException("Updating otaPackage version is prohibited!"); + } + + if (!Objects.equals(otaPackage.getTag(), otaPackageOld.getTag())) { + throw new DataValidationException("Updating otaPackage tag is prohibited!"); + } + + if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) { + throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!"); + } + + if (otaPackageOld.getFileName() != null && !otaPackageOld.getFileName().equals(otaPackage.getFileName())) { + throw new DataValidationException("Updating otaPackage file name is prohibited!"); + } + + if (otaPackageOld.getContentType() != null && !otaPackageOld.getContentType().equals(otaPackage.getContentType())) { + throw new DataValidationException("Updating otaPackage content type is prohibited!"); + } + + if (otaPackageOld.getChecksumAlgorithm() != null && !otaPackageOld.getChecksumAlgorithm().equals(otaPackage.getChecksumAlgorithm())) { + throw new DataValidationException("Updating otaPackage content type is prohibited!"); + } + + if (otaPackageOld.getChecksum() != null && !otaPackageOld.getChecksum().equals(otaPackage.getChecksum())) { + throw new DataValidationException("Updating otaPackage content type is prohibited!"); + } + + if (otaPackageOld.getDataSize() != null && !otaPackageOld.getDataSize().equals(otaPackage.getDataSize())) { + throw new DataValidationException("Updating otaPackage data size is prohibited!"); + } + + if (otaPackageOld.getUrl() != null && !otaPackageOld.getUrl().equals(otaPackage.getUrl())) { + throw new DataValidationException("Updating otaPackage URL is prohibited!"); + } + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java new file mode 100644 index 0000000000..d558c19e9e --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +public class ClientRegistrationTemplateDataValidator extends DataValidator { + + @Override + protected void validateCreate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + } + + @Override + protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + } + + @Override + protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { + if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) { + throw new DataValidationException("Provider ID should be specified!"); + } + if (clientRegistrationTemplate.getMapperConfig() == null) { + throw new DataValidationException("Mapper config should be specified!"); + } + if (clientRegistrationTemplate.getMapperConfig().getType() == null) { + throw new DataValidationException("Mapper type should be specified!"); + } + if (clientRegistrationTemplate.getMapperConfig().getBasic() == null) { + throw new DataValidationException("Basic mapper config should be specified!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java new file mode 100644 index 0000000000..f7eceec4d5 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentDescriptor; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +public class ComponentDescriptorDataValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, ComponentDescriptor plugin) { + if (plugin.getType() == null) { + throw new DataValidationException("Component type should be specified!"); + } + if (plugin.getScope() == null) { + throw new DataValidationException("Component scope should be specified!"); + } + if (StringUtils.isEmpty(plugin.getName())) { + throw new DataValidationException("Component name should be specified!"); + } + if (StringUtils.isEmpty(plugin.getClazz())) { + throw new DataValidationException("Component clazz should be specified!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java new file mode 100644 index 0000000000..753d3a8528 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.customer.CustomerServiceImpl; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; + +@Component +public class CustomerDataValidator extends DataValidator { + + @Autowired + private CustomerDao customerDao; + + @Autowired + private TenantDao tenantDao; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Override + protected void validateCreate(TenantId tenantId, Customer customer) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxCustomers = profileConfiguration.getMaxCustomers(); + + validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER); + customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( + c -> { + throw new DataValidationException("Customer with such title already exists!"); + } + ); + } + + @Override + protected void validateUpdate(TenantId tenantId, Customer customer) { + customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent( + c -> { + if (!c.getId().equals(customer.getId())) { + throw new DataValidationException("Customer with such title already exists!"); + } + } + ); + } + + @Override + protected void validateDataImpl(TenantId tenantId, Customer customer) { + if (StringUtils.isEmpty(customer.getTitle())) { + throw new DataValidationException("Customer title should be specified!"); + } + if (customer.getTitle().equals(CustomerServiceImpl.PUBLIC_CUSTOMER_TITLE)) { + throw new DataValidationException("'Public' title for customer is system reserved!"); + } + if (!StringUtils.isEmpty(customer.getEmail())) { + validateEmail(customer.getEmail()); + } + if (customer.getTenantId() == null) { + throw new DataValidationException("Customer should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(tenantId, customer.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Customer is referencing to non-existent tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java new file mode 100644 index 0000000000..ee226af853 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java @@ -0,0 +1,68 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.dashboard.DashboardDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; + +@Component +public class DashboardDataValidator extends DataValidator { + + @Autowired + private DashboardDao dashboardDao; + + @Autowired + private TenantDao tenantDao; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Override + protected void validateCreate(TenantId tenantId, Dashboard data) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxDashboards = profileConfiguration.getMaxDashboards(); + validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD); + } + + @Override + protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) { + if (StringUtils.isEmpty(dashboard.getTitle())) { + throw new DataValidationException("Dashboard title should be specified!"); + } + if (dashboard.getTenantId() == null) { + throw new DataValidationException("Dashboard should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(tenantId, dashboard.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Dashboard is referencing to non-existent tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java new file mode 100644 index 0000000000..5fca062d46 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.device.DeviceCredentialsDao; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +public class DeviceCredentialsDataValidator extends DataValidator { + + @Autowired + private DeviceCredentialsDao deviceCredentialsDao; + + @Autowired + private DeviceService deviceService; + + @Override + protected void validateCreate(TenantId tenantId, DeviceCredentials deviceCredentials) { + if (deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId()) != null) { + throw new DeviceCredentialsValidationException("Credentials for this device are already specified!"); + } + if (deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()) != null) { + throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!"); + } + } + + @Override + protected void validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) { + if (deviceCredentialsDao.findById(tenantId, deviceCredentials.getUuidId()) == null) { + throw new DeviceCredentialsValidationException("Unable to update non-existent device credentials!"); + } + DeviceCredentials existingCredentials = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()); + if (existingCredentials != null && !existingCredentials.getId().equals(deviceCredentials.getId())) { + throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!"); + } + } + + @Override + protected void validateDataImpl(TenantId tenantId, DeviceCredentials deviceCredentials) { + if (deviceCredentials.getDeviceId() == null) { + throw new DeviceCredentialsValidationException("Device credentials should be assigned to device!"); + } + if (deviceCredentials.getCredentialsType() == null) { + throw new DeviceCredentialsValidationException("Device credentials type should be specified!"); + } + if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) { + throw new DeviceCredentialsValidationException("Device credentials id should be specified!"); + } + Device device = deviceService.findDeviceById(tenantId, deviceCredentials.getDeviceId()); + if (device == null) { + throw new DeviceCredentialsValidationException("Can't assign device credentials to non-existent device!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java new file mode 100644 index 0000000000..fc991cee91 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.cache.EntitiesCacheManager; +import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.device.DeviceDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; + +import java.util.Optional; + +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; + +@Component +public class DeviceDataValidator extends DataValidator { + + @Autowired + private DeviceDao deviceDao; + + @Autowired + private TenantDao tenantDao; + + @Autowired + private CustomerDao customerDao; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Autowired + private OtaPackageService otaPackageService; + + @Autowired + private EntitiesCacheManager cacheManager; + + @Override + protected void validateCreate(TenantId tenantId, Device device) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxDevices = profileConfiguration.getMaxDevices(); + validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE); + } + + @Override + protected void validateUpdate(TenantId tenantId, Device device) { + Device old = deviceDao.findById(device.getTenantId(), device.getId().getId()); + if (old == null) { + throw new DataValidationException("Can't update non existing device!"); + } + if (!old.getName().equals(device.getName())) { + cacheManager.removeDeviceFromCacheByName(tenantId, old.getName()); + cacheManager.removeDeviceFromCacheById(tenantId, device.getId()); + } + } + + @Override + protected void validateDataImpl(TenantId tenantId, Device device) { + if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) { + throw new DataValidationException("Device name should be specified!"); + } + if (device.getTenantId() == null) { + throw new DataValidationException("Device should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(device.getTenantId(), device.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Device is referencing to non-existent tenant!"); + } + } + if (device.getCustomerId() == null) { + device.setCustomerId(new CustomerId(NULL_UUID)); + } else if (!device.getCustomerId().getId().equals(NULL_UUID)) { + Customer customer = customerDao.findById(device.getTenantId(), device.getCustomerId().getId()); + if (customer == null) { + throw new DataValidationException("Can't assign device to non-existent customer!"); + } + if (!customer.getTenantId().getId().equals(device.getTenantId().getId())) { + throw new DataValidationException("Can't assign device to customer from different tenant!"); + } + } + Optional.ofNullable(device.getDeviceData()) + .flatMap(deviceData -> Optional.ofNullable(deviceData.getTransportConfiguration())) + .ifPresent(DeviceTransportConfiguration::validate); + + if (device.getFirmwareId() != null) { + OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, device.getFirmwareId()); + if (firmware == null) { + throw new DataValidationException("Can't assign non-existent firmware!"); + } + if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) { + throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); + } + if (firmware.getData() == null && !firmware.hasUrl()) { + throw new DataValidationException("Can't assign firmware with empty data!"); + } + if (!firmware.getDeviceProfileId().equals(device.getDeviceProfileId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } + } + + if (device.getSoftwareId() != null) { + OtaPackage software = otaPackageService.findOtaPackageById(tenantId, device.getSoftwareId()); + if (software == null) { + throw new DataValidationException("Can't assign non-existent software!"); + } + if (!software.getType().equals(OtaPackageType.SOFTWARE)) { + throw new DataValidationException("Can't assign software with type: " + software.getType()); + } + if (software.getData() == null && !software.hasUrl()) { + throw new DataValidationException("Can't assign software with empty data!"); + } + if (!software.getDeviceProfileId().equals(device.getDeviceProfileId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java new file mode 100644 index 0000000000..7982682ebd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -0,0 +1,524 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.Syntax; +import com.squareup.wire.schema.Field; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; +import org.eclipse.leshan.core.util.SecurityUtil; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; +import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.device.DeviceDao; +import org.thingsboard.server.dao.device.DeviceProfileDao; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.queue.QueueService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +public class DeviceProfileDataValidator extends DataValidator { + + private static final Location LOCATION = new Location("", "", -1, -1); + private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; + private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; + private static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema"; + private static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema"; + @Autowired + private DeviceProfileDao deviceProfileDao; + @Autowired + @Lazy + private DeviceProfileService deviceProfileService; + @Autowired + private DeviceDao deviceDao; + @Autowired + private TenantDao tenantDao; + @Autowired + @Lazy + private QueueService queueService; + @Autowired + private OtaPackageService otaPackageService; + @Autowired + private RuleChainService ruleChainService; + @Autowired + private DashboardService dashboardService; + + private static String invalidSchemaProvidedMessage(String schemaName) { + return "[Transport Configuration] invalid " + schemaName + " provided!"; + } + + @Override + protected void validateDataImpl(TenantId tenantId, DeviceProfile deviceProfile) { + if (StringUtils.isEmpty(deviceProfile.getName())) { + throw new DataValidationException("Device profile name should be specified!"); + } + if (deviceProfile.getType() == null) { + throw new DataValidationException("Device profile type should be specified!"); + } + if (deviceProfile.getTransportType() == null) { + throw new DataValidationException("Device profile transport type should be specified!"); + } + if (deviceProfile.getTenantId() == null) { + throw new DataValidationException("Device profile should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(deviceProfile.getTenantId(), deviceProfile.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Device profile is referencing to non-existent tenant!"); + } + } + if (deviceProfile.isDefault()) { + DeviceProfile defaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(tenantId); + if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) { + throw new DataValidationException("Another default device profile is present in scope of current tenant!"); + } + } + if (deviceProfile.getDefaultQueueId() != null) { + Queue queue = queueService.findQueueById(tenantId, deviceProfile.getDefaultQueueId()); + if (queue == null) { + throw new DataValidationException("Device profile is referencing to non-existent queue!"); + } + } + if (deviceProfile.getProvisionType() == null) { + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + } + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + transportConfiguration.validate(); + if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = + (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + validateProtoSchemas(protoTransportPayloadConfiguration); + validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration); + validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); + } + } else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) { + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); + if (coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration) { + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + validateProtoSchemas(protoTransportPayloadConfiguration); + validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration); + validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); + } + } + } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) { + List lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap(); + if (lwM2MBootstrapServersConfigurations != null) { + validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations, + ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable()); + for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { + validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential); + } + } + } + + List profileAlarms = deviceProfile.getProfileData().getAlarms(); + + if (!CollectionUtils.isEmpty(profileAlarms)) { + Set alarmTypes = new HashSet<>(); + for (DeviceProfileAlarm alarm : profileAlarms) { + String alarmType = alarm.getAlarmType(); + if (StringUtils.isEmpty(alarmType)) { + throw new DataValidationException("Alarm rule type should be specified!"); + } + if (!alarmTypes.add(alarmType)) { + throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType)); + } + } + } + + if (deviceProfile.getDefaultRuleChainId() != null) { + RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, deviceProfile.getDefaultRuleChainId()); + if (ruleChain == null) { + throw new DataValidationException("Can't assign non-existent rule chain!"); + } + } + + if (deviceProfile.getDefaultDashboardId() != null) { + DashboardInfo dashboard = dashboardService.findDashboardInfoById(tenantId, deviceProfile.getDefaultDashboardId()); + if (dashboard == null) { + throw new DataValidationException("Can't assign non-existent dashboard!"); + } + } + + if (deviceProfile.getFirmwareId() != null) { + OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getFirmwareId()); + if (firmware == null) { + throw new DataValidationException("Can't assign non-existent firmware!"); + } + if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) { + throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); + } + if (firmware.getData() == null && !firmware.hasUrl()) { + throw new DataValidationException("Can't assign firmware with empty data!"); + } + if (!firmware.getDeviceProfileId().equals(deviceProfile.getId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } + } + + if (deviceProfile.getSoftwareId() != null) { + OtaPackage software = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getSoftwareId()); + if (software == null) { + throw new DataValidationException("Can't assign non-existent software!"); + } + if (!software.getType().equals(OtaPackageType.SOFTWARE)) { + throw new DataValidationException("Can't assign software with type: " + software.getType()); + } + if (software.getData() == null && !software.hasUrl()) { + throw new DataValidationException("Can't assign software with empty data!"); + } + if (!software.getDeviceProfileId().equals(deviceProfile.getId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } + } + } + + @Override + protected void validateUpdate(TenantId tenantId, DeviceProfile deviceProfile) { + DeviceProfile old = deviceProfileDao.findById(deviceProfile.getTenantId(), deviceProfile.getId().getId()); + if (old == null) { + throw new DataValidationException("Can't update non existing device profile!"); + } + boolean profileTypeChanged = !old.getType().equals(deviceProfile.getType()); + boolean transportTypeChanged = !old.getTransportType().equals(deviceProfile.getTransportType()); + if (profileTypeChanged || transportTypeChanged) { + Long profileDeviceCount = deviceDao.countDevicesByDeviceProfileId(deviceProfile.getTenantId(), deviceProfile.getId().getId()); + if (profileDeviceCount > 0) { + String message = null; + if (profileTypeChanged) { + message = "Can't change device profile type because devices referenced it!"; + } else if (transportTypeChanged) { + message = "Can't change device profile transport type because devices referenced it!"; + } + throw new DataValidationException(message); + } + } + } + + private void validateProtoSchemas(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { + try { + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema(), RPC_REQUEST_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcResponseProtoSchema(), RPC_RESPONSE_PROTO_SCHEMA); + } catch (Exception exception) { + throw new DataValidationException(exception.getMessage()); + } + } + + private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException { + ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); + ProtoFileElement protoFileElement; + try { + protoFileElement = schemaParser.readProtoFile(); + } catch (Exception e) { + throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage()); + } + checkProtoFileSyntax(schemaName, protoFileElement); + checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!"); + checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!"); + checkTypeElements(schemaName, protoFileElement); + } + + private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + + " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + + private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage); + } + } + + private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) { + List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types)); + checkMessageElements(schemaName, getMessageTypes(types)); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); + } + } + + private void checkFieldElements(String schemaName, List fieldElements) { + if (!fieldElements.isEmpty()) { + boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + return label != null && label.equals(Field.Label.REQUIRED); + }); + if (hasRequiredLabel) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); + } + boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); + if (hasDefaultValue) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); + } + } + } + + private void checkEnumElements(String schemaName, List enumTypes) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); + } + } + + private void checkMessageElements(String schemaName, List messageElementsList) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!"); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!"); + checkFieldElements(schemaName, messageElement.getFields()); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> { + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!"); + checkFieldElements(schemaName, oneOfElement.getFields()); + }); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes); + } + }); + } + } + + private List getMessageTypes(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + } + + private List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } + + private void validateTelemetryDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { + String deviceTelemetryProtoSchema = protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(); + Descriptors.Descriptor telemetryDynamicMessageDescriptor = protoTransportPayloadTypeConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema); + if (telemetryDynamicMessageDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Failed to get telemetryDynamicMessageDescriptor!"); + } else { + List fields = telemetryDynamicMessageDescriptor.getFields(); + if (CollectionUtils.isEmpty(fields)) { + throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " " + telemetryDynamicMessageDescriptor.getName() + " fields is empty!"); + } else if (fields.size() == 2) { + Descriptors.FieldDescriptor tsFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("ts"); + Descriptors.FieldDescriptor valuesFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("values"); + if (tsFieldDescriptor != null && valuesFieldDescriptor != null) { + if (!Descriptors.FieldDescriptor.Type.MESSAGE.equals(valuesFieldDescriptor.getType())) { + throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'values' has invalid data type. Only message type is supported!"); + } + if (!Descriptors.FieldDescriptor.Type.INT64.equals(tsFieldDescriptor.getType())) { + throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid data type. Only int64 type is supported!"); + } + if (!tsFieldDescriptor.hasOptionalKeyword()) { + throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid label. Field 'ts' should have optional keyword!"); + } + } + } + } + } + + private void validateRpcRequestDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { + DynamicMessage.Builder rpcRequestDynamicMessageBuilder = protoTransportPayloadTypeConfiguration.getRpcRequestDynamicMessageBuilder(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema()); + Descriptors.Descriptor rpcRequestDynamicMessageDescriptor = rpcRequestDynamicMessageBuilder.getDescriptorForType(); + if (rpcRequestDynamicMessageDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get rpcRequestDynamicMessageDescriptor!"); + } else { + if (CollectionUtils.isEmpty(rpcRequestDynamicMessageDescriptor.getFields()) || rpcRequestDynamicMessageDescriptor.getFields().size() != 3) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " " + rpcRequestDynamicMessageDescriptor.getName() + " message should always contains 3 fields: method, requestId and params!"); + } + Descriptors.FieldDescriptor methodFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("method"); + if (methodFieldDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: method!"); + } else { + if (!Descriptors.FieldDescriptor.Type.STRING.equals(methodFieldDescriptor.getType())) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid data type. Only string type is supported!"); + } + if (methodFieldDescriptor.isRepeated()) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid label!"); + } + } + Descriptors.FieldDescriptor requestIdFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("requestId"); + if (requestIdFieldDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: requestId!"); + } else { + if (!Descriptors.FieldDescriptor.Type.INT32.equals(requestIdFieldDescriptor.getType())) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid data type. Only int32 type is supported!"); + } + if (requestIdFieldDescriptor.isRepeated()) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid label!"); + } + } + Descriptors.FieldDescriptor paramsFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("params"); + if (paramsFieldDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: params!"); + } else { + if (paramsFieldDescriptor.isRepeated()) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'params' has invalid label!"); + } + } + } + } + + private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) { + Set uris = new HashSet<>(); + Set shortServerIds = new HashSet<>(); + for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) { + AbstractLwM2MBootstrapServerCredential serverConfig = (AbstractLwM2MBootstrapServerCredential) bootstrapServerCredential; + if (!isBootstrapServerUpdateEnable && serverConfig.isBootstrapServerIs()) { + throw new DeviceCredentialsValidationException("Bootstrap config must not include \"Bootstrap Server\". \"Include Bootstrap Server updates\" is " + isBootstrapServerUpdateEnable + "."); + } + String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server" + " shortServerId: " + serverConfig.getShortServerId() + ":"; + if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) { + throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!"); + } + if (!shortServerIds.add(serverConfig.getShortServerId())) { + throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!"); + } + String uri = serverConfig.getHost() + ":" + serverConfig.getPort(); + if (!uris.add(uri)) { + throw new DeviceCredentialsValidationException(server + " \"Host + port\" value = " + uri + ". This value must be a unique value for all servers!"); + } + Integer port; + if (LwM2MSecurityMode.NO_SEC.equals(serverConfig.getSecurityMode())) { + port = serverConfig.isBootstrapServerIs() ? 5687 : 5685; + } else { + port = serverConfig.isBootstrapServerIs() ? 5688 : 5686; + } + if (serverConfig.getPort() == null || serverConfig.getPort().intValue() != port) { + throw new DeviceCredentialsValidationException(server + " \"Port\" value = " + serverConfig.getPort() + ". This value for security " + serverConfig.getSecurityMode().name() + " must be " + port + "!"); + } + } + } + + private void validateLwm2mServersCredentialOfBootstrapForClient(LwM2MBootstrapServerCredential bootstrapServerConfig) { + String server; + switch (bootstrapServerConfig.getSecurityMode()) { + case NO_SEC: + case PSK: + break; + case RPK: + RPKLwM2MBootstrapServerCredential rpkServerCredentials = (RPKLwM2MBootstrapServerCredential) bootstrapServerConfig; + server = rpkServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; + if (StringUtils.isEmpty(rpkServerCredentials.getServerPublicKey())) { + throw new DeviceCredentialsValidationException(server + " RPK public key must be specified!"); + } + try { + String pubkRpkSever = EncryptionUtil.pubkTrimNewLines(rpkServerCredentials.getServerPublicKey()); + rpkServerCredentials.setServerPublicKey(pubkRpkSever); + SecurityUtil.publicKey.decode(rpkServerCredentials.getDecodedCServerPublicKey()); + } catch (Exception e) { + throw new DeviceCredentialsValidationException(server + " RPK public key must be in standard [RFC7250] and then encoded to Base64 format!"); + } + break; + case X509: + X509LwM2MBootstrapServerCredential x509ServerCredentials = (X509LwM2MBootstrapServerCredential) bootstrapServerConfig; + server = x509ServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server"; + if (StringUtils.isEmpty(x509ServerCredentials.getServerPublicKey())) { + throw new DeviceCredentialsValidationException(server + " X509 certificate must be specified!"); + } + + try { + String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getServerPublicKey()); + x509ServerCredentials.setServerPublicKey(certServer); + SecurityUtil.certificate.decode(x509ServerCredentials.getDecodedCServerPublicKey()); + } catch (Exception e) { + throw new DeviceCredentialsValidationException(server + " X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!"); + } + break; + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java new file mode 100644 index 0000000000..be01ec5c9b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java @@ -0,0 +1,90 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.cache.EntitiesCacheManager; +import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.edge.EdgeDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; + +@Component +@AllArgsConstructor +public class EdgeDataValidator extends DataValidator { + + private final EdgeDao edgeDao; + private final TenantDao tenantDao; + private final CustomerDao customerDao; + private final EntitiesCacheManager cacheManager; + + @Override + protected void validateCreate(TenantId tenantId, Edge edge) { + } + + @Override + protected void validateUpdate(TenantId tenantId, Edge edge) { + Edge old = edgeDao.findById(edge.getTenantId(), edge.getId().getId()); + if (!old.getName().equals(edge.getName())) { + cacheManager.removeEdgeFromCacheByName(tenantId, old.getName()); + } + } + + @Override + protected void validateDataImpl(TenantId tenantId, Edge edge) { + if (org.springframework.util.StringUtils.isEmpty(edge.getType())) { + throw new DataValidationException("Edge type should be specified!"); + } + if (org.springframework.util.StringUtils.isEmpty(edge.getName())) { + throw new DataValidationException("Edge name should be specified!"); + } + if (org.springframework.util.StringUtils.isEmpty(edge.getSecret())) { + throw new DataValidationException("Edge secret should be specified!"); + } + if (StringUtils.isEmpty(edge.getRoutingKey())) { + throw new DataValidationException("Edge routing key should be specified!"); + } + if (edge.getTenantId() == null) { + throw new DataValidationException("Edge should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(edge.getTenantId(), edge.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Edge is referencing to non-existent tenant!"); + } + } + if (edge.getCustomerId() == null) { + edge.setCustomerId(new CustomerId(NULL_UUID)); + } else if (!edge.getCustomerId().getId().equals(NULL_UUID)) { + Customer customer = customerDao.findById(edge.getTenantId(), edge.getCustomerId().getId()); + if (customer == null) { + throw new DataValidationException("Can't assign edge to non-existent customer!"); + } + if (!customer.getTenantId().getId().equals(edge.getTenantId().getId())) { + throw new DataValidationException("Can't assign edge to customer from different tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeEventDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeEventDataValidator.java new file mode 100644 index 0000000000..5c437401b3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeEventDataValidator.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +public class EdgeEventDataValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, EdgeEvent edgeEvent) { + if (edgeEvent.getEdgeId() == null) { + throw new DataValidationException("Edge id should be specified!"); + } + if (edgeEvent.getAction() == null) { + throw new DataValidationException("Edge Event action should be specified!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java new file mode 100644 index 0000000000..a62e1afc9f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java @@ -0,0 +1,88 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.entityview.EntityViewDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; + +@Component +@AllArgsConstructor +public class EntityViewDataValidator extends DataValidator { + + private final EntityViewDao entityViewDao; + private final TenantDao tenantDao; + private final CustomerDao customerDao; + + @Override + protected void validateCreate(TenantId tenantId, EntityView entityView) { + entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) + .ifPresent(e -> { + throw new DataValidationException("Entity view with such name already exists!"); + }); + } + + @Override + protected void validateUpdate(TenantId tenantId, EntityView entityView) { + entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName()) + .ifPresent(e -> { + if (!e.getUuidId().equals(entityView.getUuidId())) { + throw new DataValidationException("Entity view with such name already exists!"); + } + }); + } + + @Override + protected void validateDataImpl(TenantId tenantId, EntityView entityView) { + if (StringUtils.isEmpty(entityView.getType())) { + throw new DataValidationException("Entity View type should be specified!"); + } + if (StringUtils.isEmpty(entityView.getName())) { + throw new DataValidationException("Entity view name should be specified!"); + } + if (entityView.getTenantId() == null) { + throw new DataValidationException("Entity view should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(tenantId, entityView.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Entity view is referencing to non-existent tenant!"); + } + } + if (entityView.getCustomerId() == null) { + entityView.setCustomerId(new CustomerId(NULL_UUID)); + } else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) { + Customer customer = customerDao.findById(tenantId, entityView.getCustomerId().getId()); + if (customer == null) { + throw new DataValidationException("Can't assign entity view to non-existent customer!"); + } + if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) { + throw new DataValidationException("Can't assign entity view to customer from different tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java new file mode 100644 index 0000000000..ae7b7542a4 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; + +@Component +public class EventDataValidator extends DataValidator { + + @Override + protected void validateDataImpl(TenantId tenantId, Event event) { + if (event.getEntityId() == null) { + throw new DataValidationException("Entity id should be specified!."); + } + if (StringUtils.isEmpty(event.getType())) { + throw new DataValidationException("Event type should be specified!."); + } + if (event.getBody() == null) { + throw new DataValidationException("Event body should be specified!."); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java new file mode 100644 index 0000000000..8b261ad226 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java @@ -0,0 +1,105 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.ota.OtaPackageDao; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; + +import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE; + +@Component +public class OtaPackageDataValidator extends BaseOtaPackageDataValidator { + + @Autowired + private OtaPackageDao otaPackageDao; + + @Autowired + @Lazy + private OtaPackageService otaPackageService; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Override + protected void validateCreate(TenantId tenantId, OtaPackage otaPackage) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes(); + validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE); + } + + @Override + protected void validateDataImpl(TenantId tenantId, OtaPackage otaPackage) { + validateImpl(otaPackage); + + if (!otaPackage.hasUrl()) { + if (StringUtils.isEmpty(otaPackage.getFileName())) { + throw new DataValidationException("OtaPackage file name should be specified!"); + } + + if (StringUtils.isEmpty(otaPackage.getContentType())) { + throw new DataValidationException("OtaPackage content type should be specified!"); + } + + if (otaPackage.getChecksumAlgorithm() == null) { + throw new DataValidationException("OtaPackage checksum algorithm should be specified!"); + } + if (StringUtils.isEmpty(otaPackage.getChecksum())) { + throw new DataValidationException("OtaPackage checksum should be specified!"); + } + + String currentChecksum; + + currentChecksum = otaPackageService.generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData()); + + if (!currentChecksum.equals(otaPackage.getChecksum())) { + throw new DataValidationException("Wrong otaPackage file!"); + } + } else { + if (otaPackage.getData() != null) { + throw new DataValidationException("File can't be saved if URL present!"); + } + } + } + + @Override + protected void validateUpdate(TenantId tenantId, OtaPackage otaPackage) { + OtaPackage otaPackageOld = otaPackageDao.findById(tenantId, otaPackage.getUuidId()); + + validateUpdate(otaPackage, otaPackageOld); + + if (otaPackageOld.getData() != null && !otaPackageOld.getData().equals(otaPackage.getData())) { + throw new DataValidationException("Updating otaPackage data is prohibited!"); + } + + if (otaPackageOld.getData() == null && otaPackage.getData() != null) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes(); + validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java new file mode 100644 index 0000000000..d880dfe42c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.ota.OtaPackageInfoDao; + +@Component +public class OtaPackageInfoDataValidator extends BaseOtaPackageDataValidator { + + @Autowired + private OtaPackageInfoDao otaPackageInfoDao; + + @Override + protected void validateDataImpl(TenantId tenantId, OtaPackageInfo otaPackageInfo) { + validateImpl(otaPackageInfo); + } + + @Override + protected void validateUpdate(TenantId tenantId, OtaPackageInfo otaPackage) { + OtaPackageInfo otaPackageOld = otaPackageInfoDao.findById(tenantId, otaPackage.getUuidId()); + validateUpdate(otaPackage, otaPackageOld); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java new file mode 100644 index 0000000000..90ac54577a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java @@ -0,0 +1,82 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.resource.TbResourceDao; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; + +import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE; + +@Component +public class ResourceDataValidator extends DataValidator { + + @Autowired + private TbResourceDao resourceDao; + + @Autowired + private TenantDao tenantDao; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Override + protected void validateCreate(TenantId tenantId, TbResource resource) { + if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId)) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes(); + validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length(), TB_RESOURCE); + } + } + + @Override + protected void validateDataImpl(TenantId tenantId, TbResource resource) { + if (StringUtils.isEmpty(resource.getTitle())) { + throw new DataValidationException("Resource title should be specified!"); + } + if (resource.getResourceType() == null) { + throw new DataValidationException("Resource type should be specified!"); + } + if (StringUtils.isEmpty(resource.getFileName())) { + throw new DataValidationException("Resource file name should be specified!"); + } + if (StringUtils.isEmpty(resource.getResourceKey())) { + throw new DataValidationException("Resource key should be specified!"); + } + if (resource.getTenantId() == null) { + resource.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID)); + } + if (!resource.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { + Tenant tenant = tenantDao.findById(tenantId, resource.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Resource is referencing to non-existent tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java new file mode 100644 index 0000000000..f36b4d23fa --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java @@ -0,0 +1,88 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.rule.RuleChainDao; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; + +@Component +public class RuleChainDataValidator extends DataValidator { + + @Autowired + private RuleChainDao ruleChainDao; + + @Autowired + @Lazy + private RuleChainService ruleChainService; + + @Autowired + private TenantDao tenantDao; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Override + protected void validateCreate(TenantId tenantId, RuleChain data) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxRuleChains = profileConfiguration.getMaxRuleChains(); + validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN); + } + + @Override + protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) { + if (StringUtils.isEmpty(ruleChain.getName())) { + throw new DataValidationException("Rule chain name should be specified!"); + } + if (ruleChain.getType() == null) { + ruleChain.setType(RuleChainType.CORE); + } + if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) { + throw new DataValidationException("Rule chain should be assigned to tenant!"); + } + Tenant tenant = tenantDao.findById(tenantId, ruleChain.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Rule chain is referencing to non-existent tenant!"); + } + if (ruleChain.isRoot() && RuleChainType.CORE.equals(ruleChain.getType())) { + RuleChain rootRuleChain = ruleChainService.getRootTenantRuleChain(ruleChain.getTenantId()); + if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) { + throw new DataValidationException("Another root rule chain is present in scope of current tenant!"); + } + } + if (ruleChain.isRoot() && RuleChainType.EDGE.equals(ruleChain.getType())) { + RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(ruleChain.getTenantId()); + if (edgeTemplateRootRuleChain != null && !edgeTemplateRootRuleChain.getId().equals(ruleChain.getId())) { + throw new DataValidationException("Another edge template root rule chain is present in scope of current tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java new file mode 100644 index 0000000000..12c767a893 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +@Component +public class TenantDataValidator extends DataValidator { + + @Autowired + private TenantDao tenantDao; + + @Override + protected void validateDataImpl(TenantId tenantId, Tenant tenant) { + if (StringUtils.isEmpty(tenant.getTitle())) { + throw new DataValidationException("Tenant title should be specified!"); + } + if (!StringUtils.isEmpty(tenant.getEmail())) { + validateEmail(tenant.getEmail()); + } + } + + @Override + protected void validateUpdate(TenantId tenantId, Tenant tenant) { + Tenant old = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId.getId()); + if (old == null) { + throw new DataValidationException("Can't update non existing tenant!"); + } + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java new file mode 100644 index 0000000000..47a834434b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java @@ -0,0 +1,155 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.queue.ProcessingStrategy; +import org.thingsboard.server.common.data.queue.SubmitStrategy; +import org.thingsboard.server.common.data.queue.SubmitStrategyType; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantProfileDao; +import org.thingsboard.server.dao.tenant.TenantProfileService; + +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +@Component +public class TenantProfileDataValidator extends DataValidator { + + @Autowired + private TenantProfileDao tenantProfileDao; + + @Autowired + @Lazy + private TenantProfileService tenantProfileService; + + @Override + protected void validateDataImpl(TenantId tenantId, TenantProfile tenantProfile) { + if (StringUtils.isEmpty(tenantProfile.getName())) { + throw new DataValidationException("Tenant profile name should be specified!"); + } + if (tenantProfile.getProfileData() == null) { + throw new DataValidationException("Tenant profile data should be specified!"); + } + if (tenantProfile.getProfileData().getConfiguration() == null) { + throw new DataValidationException("Tenant profile data configuration should be specified!"); + } + if (tenantProfile.isDefault()) { + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId); + if (defaultTenantProfile != null && !defaultTenantProfile.getId().equals(tenantProfile.getId())) { + throw new DataValidationException("Another default tenant profile is present!"); + } + } + + if (tenantProfile.isIsolatedTbRuleEngine()) { + List queueConfiguration = tenantProfile.getProfileData().getQueueConfiguration(); + if (queueConfiguration == null) { + throw new DataValidationException("Tenant profile data queue configuration should be specified!"); + } + + Optional mainQueueConfig = + queueConfiguration + .stream() + .filter(q -> q.getName().equals("Main")) + .findAny(); + if (mainQueueConfig.isEmpty()) { + throw new DataValidationException("Main queue configuration should be specified!"); + } + + queueConfiguration.forEach(this::validateQueueConfiguration); + + Set queueNames = new HashSet<>(queueConfiguration.size()); + + queueConfiguration.forEach(q -> { + String name = q.getName(); + if (queueNames.contains(name)) { + throw new DataValidationException(String.format("Queue configuration name '%s' already present!", name)); + } else { + queueNames.add(name); + } + }); + } + } + + @Override + protected void validateUpdate(TenantId tenantId, TenantProfile tenantProfile) { + TenantProfile old = tenantProfileDao.findById(TenantId.SYS_TENANT_ID, tenantProfile.getId().getId()); + if (old == null) { + throw new DataValidationException("Can't update non existing tenant profile!"); + } else if (old.isIsolatedTbRuleEngine() != tenantProfile.isIsolatedTbRuleEngine()) { + throw new DataValidationException("Can't update isolatedTbRuleEngine property!"); + } else if (old.isIsolatedTbCore() != tenantProfile.isIsolatedTbCore()) { + throw new DataValidationException("Can't update isolatedTbCore property!"); + } + } + + private void validateQueueConfiguration(TenantProfileQueueConfiguration queue) { + if (StringUtils.isEmpty(queue.getName())) { + throw new DataValidationException("Queue name should be specified!"); + } + if (StringUtils.isBlank(queue.getTopic())) { + throw new DataValidationException("Queue topic should be non empty and without spaces!"); + } + if (queue.getPollInterval() < 1) { + throw new DataValidationException("Queue poll interval should be more then 0!"); + } + if (queue.getPartitions() < 1) { + throw new DataValidationException("Queue partitions should be more then 0!"); + } + if (queue.getPackProcessingTimeout() < 1) { + throw new DataValidationException("Queue pack processing timeout should be more then 0!"); + } + + SubmitStrategy submitStrategy = queue.getSubmitStrategy(); + if (submitStrategy == null) { + throw new DataValidationException("Queue submit strategy can't be null!"); + } + if (submitStrategy.getType() == null) { + throw new DataValidationException("Queue submit strategy type can't be null!"); + } + if (submitStrategy.getType() == SubmitStrategyType.BATCH && submitStrategy.getBatchSize() < 1) { + throw new DataValidationException("Queue submit strategy batch size should be more then 0!"); + } + ProcessingStrategy processingStrategy = queue.getProcessingStrategy(); + if (processingStrategy == null) { + throw new DataValidationException("Queue processing strategy can't be null!"); + } + if (processingStrategy.getType() == null) { + throw new DataValidationException("Queue processing strategy type can't be null!"); + } + if (processingStrategy.getRetries() < 0) { + throw new DataValidationException("Queue processing strategy retries can't be less then 0!"); + } + if (processingStrategy.getFailurePercentage() < 0 || processingStrategy.getFailurePercentage() > 100) { + throw new DataValidationException("Queue processing strategy failure percentage should be in a range from 0 to 100!"); + } + if (processingStrategy.getPauseBetweenRetries() < 0) { + throw new DataValidationException("Queue processing strategy pause between retries can't be less then 0!"); + } + if (processingStrategy.getMaxPauseBetweenRetries() < processingStrategy.getPauseBetweenRetries()) { + throw new DataValidationException("Queue processing strategy MAX pause between retries can't be less then pause between retries!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java new file mode 100644 index 0000000000..5920ad552f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java @@ -0,0 +1,68 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.user.UserCredentialsDao; +import org.thingsboard.server.dao.user.UserService; + +@Component +public class UserCredentialsDataValidator extends DataValidator { + + @Autowired + private UserCredentialsDao userCredentialsDao; + + @Autowired + @Lazy + private UserService userService; + + @Override + protected void validateCreate(TenantId tenantId, UserCredentials userCredentials) { + throw new IncorrectParameterException("Creation of new user credentials is prohibited."); + } + + @Override + protected void validateDataImpl(TenantId tenantId, UserCredentials userCredentials) { + if (userCredentials.getUserId() == null) { + throw new DataValidationException("User credentials should be assigned to user!"); + } + if (userCredentials.isEnabled()) { + if (StringUtils.isEmpty(userCredentials.getPassword())) { + throw new DataValidationException("Enabled user credentials should have password!"); + } + if (StringUtils.isNotEmpty(userCredentials.getActivateToken())) { + throw new DataValidationException("Enabled user credentials can't have activate token!"); + } + } + UserCredentials existingUserCredentialsEntity = userCredentialsDao.findById(tenantId, userCredentials.getId().getId()); + if (existingUserCredentialsEntity == null) { + throw new DataValidationException("Unable to update non-existent user credentials!"); + } + User user = userService.findUserById(tenantId, userCredentials.getUserId()); + if (user == null) { + throw new DataValidationException("Can't assign user credentials to non-existent user!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java new file mode 100644 index 0000000000..94b894acce --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java @@ -0,0 +1,136 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.user.UserDao; +import org.thingsboard.server.dao.user.UserService; + +@Component +public class UserDataValidator extends DataValidator { + + @Autowired + private UserDao userDao; + + @Autowired + @Lazy + private UserService userService; + + @Autowired + private TenantDao tenantDao; + + @Autowired + private CustomerDao customerDao; + + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + @Override + protected void validateCreate(TenantId tenantId, User user) { + if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxUsers = profileConfiguration.getMaxUsers(); + validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER); + } + } + + @Override + protected void validateDataImpl(TenantId requestTenantId, User user) { + if (StringUtils.isEmpty(user.getEmail())) { + throw new DataValidationException("User email should be specified!"); + } + + validateEmail(user.getEmail()); + + Authority authority = user.getAuthority(); + if (authority == null) { + throw new DataValidationException("User authority isn't defined!"); + } + TenantId tenantId = user.getTenantId(); + if (tenantId == null) { + tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID); + user.setTenantId(tenantId); + } + CustomerId customerId = user.getCustomerId(); + if (customerId == null) { + customerId = new CustomerId(ModelConstants.NULL_UUID); + user.setCustomerId(customerId); + } + + switch (authority) { + case SYS_ADMIN: + if (!tenantId.getId().equals(ModelConstants.NULL_UUID) + || !customerId.getId().equals(ModelConstants.NULL_UUID)) { + throw new DataValidationException("System administrator can't be assigned neither to tenant nor to customer!"); + } + break; + case TENANT_ADMIN: + if (tenantId.getId().equals(ModelConstants.NULL_UUID)) { + throw new DataValidationException("Tenant administrator should be assigned to tenant!"); + } else if (!customerId.getId().equals(ModelConstants.NULL_UUID)) { + throw new DataValidationException("Tenant administrator can't be assigned to customer!"); + } + break; + case CUSTOMER_USER: + if (tenantId.getId().equals(ModelConstants.NULL_UUID) + || customerId.getId().equals(ModelConstants.NULL_UUID)) { + throw new DataValidationException("Customer user should be assigned to customer!"); + } + break; + default: + break; + } + + User existentUserWithEmail = userService.findUserByEmail(tenantId, user.getEmail()); + if (existentUserWithEmail != null && !isSameData(existentUserWithEmail, user)) { + throw new DataValidationException("User with email '" + user.getEmail() + "' " + + " already present in database!"); + } + if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) { + Tenant tenant = tenantDao.findById(tenantId, user.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("User is referencing to non-existent tenant!"); + } + } + if (!customerId.getId().equals(ModelConstants.NULL_UUID)) { + Customer customer = customerDao.findById(tenantId, user.getCustomerId().getId()); + if (customer == null) { + throw new DataValidationException("User is referencing to non-existent customer!"); + } else if (!customer.getTenantId().getId().equals(tenantId.getId())) { + throw new DataValidationException("User can't be assigned to customer from different tenant!"); + } + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java new file mode 100644 index 0000000000..ec3be14866 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java @@ -0,0 +1,98 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.widget.WidgetTypeDao; +import org.thingsboard.server.dao.widget.WidgetsBundleDao; + +@Component +@AllArgsConstructor +public class WidgetTypeDataValidator extends DataValidator { + + private final WidgetTypeDao widgetTypeDao; + private final TenantDao tenantDao; + private final WidgetsBundleDao widgetsBundleDao; + + @Override + protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { + if (StringUtils.isEmpty(widgetTypeDetails.getName())) { + throw new DataValidationException("Widgets type name should be specified!"); + } + if (StringUtils.isEmpty(widgetTypeDetails.getBundleAlias())) { + throw new DataValidationException("Widgets type bundle alias should be specified!"); + } + if (widgetTypeDetails.getDescriptor() == null || widgetTypeDetails.getDescriptor().size() == 0) { + throw new DataValidationException("Widgets type descriptor can't be empty!"); + } + if (widgetTypeDetails.getTenantId() == null) { + widgetTypeDetails.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID)); + } + if (!widgetTypeDetails.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { + Tenant tenant = tenantDao.findById(tenantId, widgetTypeDetails.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Widget type is referencing to non-existent tenant!"); + } + } + } + + @Override + protected void validateCreate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { + WidgetsBundle widgetsBundle = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias()); + if (widgetsBundle == null) { + throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!"); + } + String alias = widgetTypeDetails.getAlias(); + if (alias == null || alias.trim().isEmpty()) { + alias = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_"); + } + String originalAlias = alias; + int c = 1; + WidgetType withSameAlias; + do { + withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias(), alias); + if (withSameAlias != null) { + alias = originalAlias + (++c); + } + } while (withSameAlias != null); + widgetTypeDetails.setAlias(alias); + } + + @Override + protected void validateUpdate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { + WidgetType storedWidgetType = widgetTypeDao.findById(tenantId, widgetTypeDetails.getId().getId()); + if (!storedWidgetType.getTenantId().getId().equals(widgetTypeDetails.getTenantId().getId())) { + throw new DataValidationException("Can't move existing widget type to different tenant!"); + } + if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) { + throw new DataValidationException("Update of widget type bundle alias is prohibited!"); + } + if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) { + throw new DataValidationException("Update of widget type alias is prohibited!"); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java new file mode 100644 index 0000000000..d5b1a1149c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2022 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.dao.service.validator; + +import lombok.AllArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.widget.WidgetsBundleDao; + +@Component +@AllArgsConstructor +public class WidgetsBundleDataValidator extends DataValidator { + + private final WidgetsBundleDao widgetsBundleDao; + private final TenantDao tenantDao; + + @Override + protected void validateDataImpl(TenantId tenantId, WidgetsBundle widgetsBundle) { + if (StringUtils.isEmpty(widgetsBundle.getTitle())) { + throw new DataValidationException("Widgets bundle title should be specified!"); + } + if (widgetsBundle.getTenantId() == null) { + widgetsBundle.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID)); + } + if (!widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { + Tenant tenant = tenantDao.findById(tenantId, widgetsBundle.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!"); + } + } + } + + @Override + protected void validateCreate(TenantId tenantId, WidgetsBundle widgetsBundle) { + String alias = widgetsBundle.getAlias(); + if (alias == null || alias.trim().isEmpty()) { + alias = widgetsBundle.getTitle().toLowerCase().replaceAll("\\W+", "_"); + } + String originalAlias = alias; + int c = 1; + WidgetsBundle withSameAlias; + do { + withSameAlias = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetsBundle.getTenantId().getId(), alias); + if (withSameAlias != null) { + alias = originalAlias + (++c); + } + } while (withSameAlias != null); + widgetsBundle.setAlias(alias); + } + + @Override + protected void validateUpdate(TenantId tenantId, WidgetsBundle widgetsBundle) { + WidgetsBundle storedWidgetsBundle = widgetsBundleDao.findById(tenantId, widgetsBundle.getId().getId()); + if (!storedWidgetsBundle.getTenantId().getId().equals(widgetsBundle.getTenantId().getId())) { + throw new DataValidationException("Can't move existing widgets bundle to different tenant!"); + } + if (!storedWidgetsBundle.getAlias().equals(widgetsBundle.getAlias())) { + throw new DataValidationException("Update of widgets bundle alias is prohibited!"); + } + } +} 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 fdca0178f5..bf4c71d296 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 @@ -17,13 +17,11 @@ package org.thingsboard.server.dao.settings; import com.fasterxml.jackson.databind.node.ObjectNode; 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.AdminSettings; import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; @@ -34,6 +32,9 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { @Autowired private AdminSettingsDao adminSettingsDao; + @Autowired + private DataValidator adminSettingsValidator; + @Override public AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId) { log.trace("Executing findAdminSettingsById [{}]", adminSettingsId); @@ -62,37 +63,4 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { return adminSettingsDao.save(tenantId, adminSettings); } - private DataValidator adminSettingsValidator = - new DataValidator() { - - @Override - protected void validateCreate(TenantId tenantId, AdminSettings adminSettings) { - AdminSettings existentAdminSettingsWithKey = findAdminSettingsByKey(tenantId, adminSettings.getKey()); - if (existentAdminSettingsWithKey != null) { - throw new DataValidationException("Admin settings with such name already exists!"); - } - } - - @Override - protected void validateUpdate(TenantId tenantId, AdminSettings adminSettings) { - AdminSettings existentAdminSettings = findAdminSettingsById(tenantId, adminSettings.getId()); - if (existentAdminSettings != null) { - if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) { - throw new DataValidationException("Changing key of admin settings entry is prohibited!"); - } - } - } - - - @Override - protected void validateDataImpl(TenantId tenantId, AdminSettings adminSettings) { - if (StringUtils.isEmpty(adminSettings.getKey())) { - throw new DataValidationException("Key should be specified!"); - } - if (adminSettings.getJsonValue() == null) { - throw new DataValidationException("Json value should be specified!"); - } - } - }; - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 7761fddf38..3c996ff6f5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -19,7 +19,7 @@ import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; @@ -41,7 +41,7 @@ public abstract class JpaAbstractDao, D> protected abstract Class getEntityClass(); - protected abstract CrudRepository getCrudRepository(); + protected abstract JpaRepository getRepository(); protected void setSearchText(E entity) { } @@ -63,52 +63,52 @@ public abstract class JpaAbstractDao, D> entity.setUuid(uuid); entity.setCreatedTime(Uuids.unixTimestamp(uuid)); } - entity = getCrudRepository().save(entity); + entity = getRepository().save(entity); return DaoUtil.getData(entity); } @Override public D findById(TenantId tenantId, UUID key) { log.debug("Get entity by key {}", key); - Optional entity = getCrudRepository().findById(key); + Optional entity = getRepository().findById(key); return DaoUtil.getData(entity); } @Override public ListenableFuture findByIdAsync(TenantId tenantId, UUID key) { log.debug("Get entity by key async {}", key); - return service.submit(() -> DaoUtil.getData(getCrudRepository().findById(key))); + return service.submit(() -> DaoUtil.getData(getRepository().findById(key))); } @Override public boolean existsById(TenantId tenantId, UUID key) { log.debug("Exists by key {}", key); - return getCrudRepository().existsById(key); + return getRepository().existsById(key); } @Override public ListenableFuture existsByIdAsync(TenantId tenantId, UUID key) { log.debug("Exists by key async {}", key); - return service.submit(() -> getCrudRepository().existsById(key)); + return service.submit(() -> getRepository().existsById(key)); } @Override @Transactional public boolean removeById(TenantId tenantId, UUID id) { - getCrudRepository().deleteById(id); + getRepository().deleteById(id); log.debug("Remove request: {}", id); - return !getCrudRepository().existsById(id); + return !getRepository().existsById(id); } @Transactional public void removeAllByIds(Collection ids) { - CrudRepository repository = getCrudRepository(); + JpaRepository repository = getRepository(); ids.forEach(repository::deleteById); } @Override public List find(TenantId tenantId) { - List entities = Lists.newArrayList(getCrudRepository().findAll()); + List entities = Lists.newArrayList(getRepository().findAll()); return DaoUtil.convertDataList(entities); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index 77149604ef..beadcddf31 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -17,13 +17,11 @@ package org.thingsboard.server.dao.sql.alarm; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmInfoEntity; @@ -34,7 +32,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/21/2017. */ -public interface AlarmRepository extends CrudRepository { +public interface AlarmRepository extends JpaRepository { @Query("SELECT a FROM AlarmEntity a WHERE a.originatorId = :originatorId AND a.type = :alarmType ORDER BY a.startTs DESC") List findLatestByOriginatorAndType(@Param("originatorId") UUID originatorId, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java index e7e2951c0e..43d43a5f8e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.sql.alarm; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.EntityAlarmCompositeKey; import org.thingsboard.server.dao.model.sql.EntityAlarmEntity; @@ -23,7 +23,7 @@ import org.thingsboard.server.dao.model.sql.EntityAlarmEntity; import java.util.List; import java.util.UUID; -public interface EntityAlarmRepository extends CrudRepository { +public interface EntityAlarmRepository extends JpaRepository { List findAllByAlarmId(UUID alarmId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 03f944a68a..cdf04f47c5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -19,7 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -39,7 +39,6 @@ import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.EntityAlarmEntity; -import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sql.query.AlarmQueryRepository; @@ -72,7 +71,7 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return alarmRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index 39ab53f9ce..833fcc2de8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -17,12 +17,11 @@ package org.thingsboard.server.dao.sql.asset; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.AssetEntity; import org.thingsboard.server.dao.model.sql.AssetInfoEntity; -import org.thingsboard.server.dao.model.sql.RuleChainEntity; import java.util.List; import java.util.UUID; @@ -30,7 +29,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/21/2017. */ -public interface AssetRepository extends PagingAndSortingRepository { +public interface AssetRepository extends JpaRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " + "FROM AssetEntity a " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index 7efedbb6db..c9752c5390 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -15,29 +15,22 @@ */ package org.thingsboard.server.dao.sql.asset; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; -import org.thingsboard.server.common.data.id.EdgeId; 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.page.TimePageLink; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.model.sql.AssetEntity; import org.thingsboard.server.dao.model.sql.AssetInfoEntity; -import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.ArrayList; @@ -65,7 +58,7 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return assetRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index ffb49c0826..d2079d5061 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.dao.sql.attributes; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.EntityType; @@ -27,7 +27,7 @@ import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import java.util.List; import java.util.UUID; -public interface AttributeKvRepository extends CrudRepository { +public interface AttributeKvRepository extends JpaRepository { @Query("SELECT a FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + "AND a.id.entityId = :entityId " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java index 86d733d671..8971a900d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/AuditLogRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.audit; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.audit.ActionType; @@ -27,7 +27,7 @@ import org.thingsboard.server.dao.model.sql.AuditLogEntity; import java.util.List; import java.util.UUID; -public interface AuditLogRepository extends PagingAndSortingRepository { +public interface AuditLogRepository extends JpaRepository { @Query("SELECT a FROM AuditLogEntity a WHERE " + "a.tenantId = :tenantId " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java index abfd43a12a..9c72bec397 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.audit; import com.google.common.util.concurrent.ListenableFuture; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; @@ -47,7 +47,7 @@ public class JpaAuditLogDao extends JpaAbstractDao imp } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return auditLogRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java index d760d4107a..1a549436d0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.component; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -29,7 +29,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/6/2017. */ -public interface ComponentDescriptorRepository extends PagingAndSortingRepository { +public interface ComponentDescriptorRepository extends JpaRepository { ComponentDescriptorEntity findByClazz(String clazz); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java index 0d4195d2e0..c1102fe9ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.component; import com.datastax.oss.driver.api.core.uuid.Uuids; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.id.ComponentDescriptorId; @@ -55,7 +55,7 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return componentDescriptorRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java index 223ae36d6c..61cd9afac7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/CustomerRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.customer; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.CustomerEntity; @@ -27,7 +27,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/6/2017. */ -public interface CustomerRepository extends PagingAndSortingRepository { +public interface CustomerRepository extends JpaRepository { @Query("SELECT c FROM CustomerEntity c WHERE c.tenantId = :tenantId " + "AND LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java index 50de7e0ae3..02311df61c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.customer; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.id.TenantId; @@ -46,7 +46,7 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return customerRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index a1ef3c4f77..a9bd93b168 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.dashboard; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.DashboardInfoEntity; @@ -27,7 +27,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/6/2017. */ -public interface DashboardInfoRepository extends PagingAndSortingRepository { +public interface DashboardInfoRepository extends JpaRepository { DashboardInfoEntity findFirstByTenantIdAndTitle(UUID tenantId, String title); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java index a316322853..36518887d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.sql.dashboard; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.DashboardEntity; import java.util.UUID; @@ -23,7 +23,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/6/2017. */ -public interface DashboardRepository extends CrudRepository { +public interface DashboardRepository extends JpaRepository { Long countByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java index 6a1d2555b6..98edcaa1fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.dashboard; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.id.TenantId; @@ -41,7 +41,7 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return dashboardRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 9a5ee268f3..d98312e7d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.dashboard; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.page.PageData; @@ -26,7 +26,6 @@ import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.dashboard.DashboardInfoDao; import org.thingsboard.server.dao.model.sql.DashboardInfoEntity; -import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.ArrayList; @@ -50,7 +49,7 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return dashboardInfoRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java index 0b298ff3a4..e88b09a2bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.device; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.id.TenantId; @@ -43,7 +43,7 @@ public class JpaDeviceCredentialsDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return deviceCredentialsRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 0b629eb542..1d75690e50 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -20,7 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; @@ -63,7 +63,7 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return deviceRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index c6997aecff..80513d77c6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.device; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.DeviceProfile; @@ -46,7 +46,7 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return deviceProfileRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java new file mode 100644 index 0000000000..c67aaabbb1 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventInsertRepository.java @@ -0,0 +1,79 @@ +/** + * Copyright © 2016-2022 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.dao.sql.edge; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; +import org.thingsboard.server.dao.model.sql.EdgeEventEntity; +import org.thingsboard.server.dao.util.PsqlDao; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + +@PsqlDao +@Repository +@Transactional +public class EdgeEventInsertRepository { + + private static final String INSERT = + "INSERT INTO edge_event (id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT DO NOTHING;"; + + @Autowired + protected JdbcTemplate jdbcTemplate; + + @Autowired + private TransactionTemplate transactionTemplate; + + protected void save(List entities) { + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + jdbcTemplate.batchUpdate(INSERT, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + EdgeEventEntity edgeEvent = entities.get(i); + ps.setObject(1, edgeEvent.getId()); + ps.setLong(2, edgeEvent.getCreatedTime()); + ps.setObject(3, edgeEvent.getEdgeId()); + ps.setString(4, edgeEvent.getEdgeEventType().name()); + ps.setString(5, edgeEvent.getEdgeEventUid()); + ps.setObject(6, edgeEvent.getEntityId()); + ps.setString(7, edgeEvent.getEdgeEventAction().name()); + ps.setString(8, edgeEvent.getEntityBody() != null + ? edgeEvent.getEntityBody().toString() + : null); + ps.setObject(9, edgeEvent.getTenantId()); + ps.setLong(10, edgeEvent.getTs()); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + } + }); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventRepository.java index 936fe59806..ef610e8e92 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeEventRepository.java @@ -17,15 +17,15 @@ package org.thingsboard.server.dao.sql.edge; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.EdgeEventEntity; import java.util.UUID; -public interface EdgeEventRepository extends PagingAndSortingRepository, JpaSpecificationExecutor { +public interface EdgeEventRepository extends JpaRepository, JpaSpecificationExecutor { @Query("SELECT e FROM EdgeEventEntity e WHERE " + "e.tenantId = :tenantId " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java index b253ca837d..3952834e8b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java @@ -17,17 +17,16 @@ package org.thingsboard.server.dao.sql.edge; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.EdgeEntity; import org.thingsboard.server.dao.model.sql.EdgeInfoEntity; -import org.thingsboard.server.dao.model.sql.RuleChainEntity; import java.util.List; import java.util.UUID; -public interface EdgeRepository extends PagingAndSortingRepository { +public interface EdgeRepository extends JpaRepository { @Query("SELECT d FROM EdgeEntity d WHERE d.tenantId = :tenantId " + "AND d.customerId = :customerId " + 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 78b57613c7..5ecb60e744 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 @@ -16,33 +16,38 @@ package org.thingsboard.server.dao.sql.edge; import com.datastax.oss.driver.api.core.uuid.Uuids; +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.data.repository.CrudRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeEventId; 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.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.edge.EdgeEventDao; import org.thingsboard.server.dao.model.sql.EdgeEventEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; +import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.Comparator; import java.util.Objects; -import java.util.Optional; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -52,81 +57,94 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao readWriteLocks = new ConcurrentHashMap<>(); + @Autowired + ScheduledLogExecutorComponent logExecutor; + + @Autowired + private StatsFactory statsFactory; + + @Value("${sql.edge_events.batch_size:1000}") + private int batchSize; + + @Value("${sql.edge_events.batch_max_delay:100}") + private long maxDelay; + + @Value("${sql.edge_events.stats_print_interval_ms:10000}") + private long statsPrintIntervalMs; + + @Value("${sql.edge_events.batch_threads:3}") + private int batchThreads; + + private TbSqlBlockingQueueWrapper queue; @Autowired private EdgeEventRepository edgeEventRepository; + @Autowired + private EdgeEventInsertRepository edgeEventInsertRepository; + @Override protected Class getEntityClass() { return EdgeEventEntity.class; } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return edgeEventRepository; } - @Override - 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()); + @PostConstruct + private void init() { + TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder() + .logName("Edge Events") + .batchSize(batchSize) + .maxDelay(maxDelay) + .statsPrintIntervalMs(statsPrintIntervalMs) + .statsNamePrefix("edge.events") + .batchSortEnabled(true) + .build(); + Function hashcodeFunction = entity -> { + if (entity.getEntityId() != null) { + return entity.getEntityId().hashCode(); + } else { + return NULL_UUID.hashCode(); } - return save(new EdgeEventEntity(edgeEvent)).orElse(null); - } finally { - readWriteLock.unlock(); + }; + queue = new TbSqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory); + queue.init(logExecutor, v -> edgeEventInsertRepository.save(v), + Comparator.comparing(EdgeEventEntity::getTs) + ); + } + + @PreDestroy + private void destroy() { + if (queue != null) { + queue.destroy(); } } @Override - public PageData findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) { - final Lock readWriteLock = readWriteLocks.computeIfAbsent(edgeId, id -> new ReentrantLock()); - readWriteLock.lock(); - try { - if (withTsUpdate) { - return DaoUtil.toPageData( - edgeEventRepository - .findEdgeEventsByTenantIdAndEdgeId( - tenantId, - edgeId.getId(), - Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getStartTime(), - pageLink.getEndTime(), - DaoUtil.toPageable(pageLink))); + public ListenableFuture 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 { - return DaoUtil.toPageData( - edgeEventRepository - .findEdgeEventsByTenantIdAndEdgeIdWithoutTimeseriesUpdated( - tenantId, - edgeId.getId(), - Objects.toString(pageLink.getTextSearch(), ""), - pageLink.getStartTime(), - pageLink.getEndTime(), - DaoUtil.toPageable(pageLink))); - + edgeEvent.setCreatedTime(System.currentTimeMillis()); } - } finally { - readWriteLock.unlock(); } + if (StringUtils.isEmpty(edgeEvent.getUid())) { + edgeEvent.setUid(edgeEvent.getId().toString()); + } + return save(new EdgeEventEntity(edgeEvent)); } - public Optional save(EdgeEventEntity entity) { + private ListenableFuture save(EdgeEventEntity entity) { log.debug("Save edge event [{}] ", entity); if (entity.getTenantId() == null) { log.trace("Save system edge event with predefined id {}", systemTenantId); @@ -135,7 +153,39 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao addToQueue(EdgeEventEntity entity) { + return queue.add(entity); + } + + + @Override + public PageData findEdgeEvents(UUID tenantId, EdgeId edgeId, TimePageLink pageLink, boolean withTsUpdate) { + if (withTsUpdate) { + return DaoUtil.toPageData( + edgeEventRepository + .findEdgeEventsByTenantIdAndEdgeId( + tenantId, + edgeId.getId(), + Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getStartTime(), + pageLink.getEndTime(), + DaoUtil.toPageable(pageLink))); + } else { + return DaoUtil.toPageData( + edgeEventRepository + .findEdgeEventsByTenantIdAndEdgeIdWithoutTimeseriesUpdated( + tenantId, + edgeId.getId(), + Objects.toString(pageLink.getTextSearch(), ""), + pageLink.getStartTime(), + pageLink.getEndTime(), + DaoUtil.toPageable(pageLink))); + + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java index 91f4a67040..4be9c08849 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java @@ -18,7 +18,7 @@ package org.thingsboard.server.dao.sql.edge; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -53,7 +53,7 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao imple } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return edgeRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java index 7c3d1c9eaf..7a88aa3c5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java @@ -17,10 +17,9 @@ package org.thingsboard.server.dao.sql.entityview; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; -import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.model.sql.EntityViewEntity; import org.thingsboard.server.dao.model.sql.EntityViewInfoEntity; @@ -30,7 +29,7 @@ import java.util.UUID; /** * Created by Victor Basanets on 8/31/2017. */ -public interface EntityViewRepository extends PagingAndSortingRepository { +public interface EntityViewRepository extends JpaRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.EntityViewInfoEntity(e, c.title, c.additionalInfo) " + "FROM EntityViewEntity e " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java index b61de44cea..8c515f13c0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/JpaEntityViewDao.java @@ -18,7 +18,7 @@ package org.thingsboard.server.dao.sql.entityview; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -57,7 +57,7 @@ public class JpaEntityViewDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return entityViewRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java deleted file mode 100644 index d3e631e887..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright © 2016-2022 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.dao.sql.event; - -import lombok.extern.slf4j.Slf4j; -import org.hibernate.exception.ConstraintViolationException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.transaction.PlatformTransactionManager; -import org.springframework.transaction.TransactionDefinition; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.DefaultTransactionDefinition; -import org.thingsboard.server.dao.model.sql.EventEntity; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; - -@Slf4j -public abstract class AbstractEventInsertRepository implements EventInsertRepository { - - @PersistenceContext - protected EntityManager entityManager; - - @Autowired - protected PlatformTransactionManager transactionManager; - - protected EventEntity saveAndGet(EventEntity entity, String insertOrUpdateOnPrimaryKeyConflict, String insertOrUpdateOnUniqueKeyConflict) { - EventEntity eventEntity = null; - TransactionStatus insertTransaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRED); - try { - eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnPrimaryKeyConflict); - transactionManager.commit(insertTransaction); - } catch (Throwable throwable) { - transactionManager.rollback(insertTransaction); - if (throwable.getCause() instanceof ConstraintViolationException) { - log.trace("Insert request leaded in a violation of a defined integrity constraint {} for Entity with entityId {} and entityType {}", throwable.getMessage(), entity.getEventUid(), entity.getEventType()); - TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW); - try { - eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict); - transactionManager.commit(transaction); - } catch (Throwable th) { - log.trace("Could not execute the update statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); - transactionManager.rollback(transaction); - } - } else { - log.trace("Could not execute the insert statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType()); - } - } - return eventEntity; - } - - @Modifying - protected abstract EventEntity doProcessSaveOrUpdate(EventEntity entity, String query); - - protected Query getQuery(EventEntity entity, String query) { - return entityManager.createNativeQuery(query, EventEntity.class) - .setParameter("id", entity.getUuid()) - .setParameter("created_time", entity.getCreatedTime()) - .setParameter("body", entity.getBody().toString()) - .setParameter("entity_id", entity.getEntityId()) - .setParameter("entity_type", entity.getEntityType().name()) - .setParameter("event_type", entity.getEventType()) - .setParameter("event_uid", entity.getEventUid()) - .setParameter("tenant_id", entity.getTenantId()) - .setParameter("ts", entity.getTs()); - } - - private EventEntity processSaveOrUpdate(EventEntity entity, String query) { - return doProcessSaveOrUpdate(entity, query); - } - - private TransactionStatus getTransactionStatus(int propagationRequired) { - DefaultTransactionDefinition insertDefinition = new DefaultTransactionDefinition(); - insertDefinition.setPropagationBehavior(propagationRequired); - return transactionManager.getTransaction(insertDefinition); - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventCleanupRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventCleanupRepository.java index 5562443474..757d69f794 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventCleanupRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventCleanupRepository.java @@ -17,6 +17,6 @@ package org.thingsboard.server.dao.sql.event; public interface EventCleanupRepository { - void cleanupEvents(long otherEventsTtl, long debugEventsTtl); + void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java index 866a9b5483..1f2d4c01be 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java @@ -15,10 +15,78 @@ */ package org.thingsboard.server.dao.sql.event; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Repository; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; import org.thingsboard.server.dao.model.sql.EventEntity; +import org.thingsboard.server.dao.util.PsqlDao; -public interface EventInsertRepository { +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.regex.Pattern; - EventEntity saveOrUpdate(EventEntity entity); +@PsqlDao +@Repository +@Transactional +public class EventInsertRepository { + private static final ThreadLocal PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE))); + + private static final String EMPTY_STR = ""; + + private static final String INSERT = + "INSERT INTO event (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) " + + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT DO NOTHING;"; + + @Autowired + protected JdbcTemplate jdbcTemplate; + + @Autowired + private TransactionTemplate transactionTemplate; + + @Value("${sql.remove_null_chars:true}") + private boolean removeNullChars; + + protected void save(List entities) { + transactionTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + jdbcTemplate.batchUpdate(INSERT, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + EventEntity event = entities.get(i); + ps.setObject(1, event.getId()); + ps.setLong(2, event.getCreatedTime()); + ps.setString(3, replaceNullChars(event.getBody().toString())); + ps.setObject(4, event.getEntityId()); + ps.setString(5, event.getEntityType().name()); + ps.setString(6, event.getEventType()); + ps.setString(7, event.getEventUid()); + ps.setObject(8, event.getTenantId()); + ps.setLong(9, event.getTs()); + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); + } + }); + } + + private String replaceNullChars(String strValue) { + if (removeNullChars && strValue != null) { + return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR); + } + return strValue; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java index 41bd9ef9cf..e36b82f1e8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/EventRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.event; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sql.EventEntity; @@ -29,7 +29,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/3/2017. */ -public interface EventRepository extends PagingAndSortingRepository { +public interface EventRepository extends JpaRepository { EventEntity findByTenantIdAndEntityTypeAndEntityIdAndEventTypeAndEventUid(UUID tenantId, EntityType entityType, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventCleanupRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventCleanupRepository.java deleted file mode 100644 index f7acc8af77..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventCleanupRepository.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright © 2016-2022 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.dao.sql.event; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Repository; -import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; -import org.thingsboard.server.dao.util.HsqlDao; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.concurrent.TimeUnit; - -@Slf4j -@HsqlDao -@Repository -public class HsqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository { - - @Override - public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { - long otherExpirationTime = System.currentTimeMillis() - otherEventsTtl * 1000; - try (Connection connection = dataSource.getConnection(); - PreparedStatement stmt = connection.prepareStatement("DELETE FROM event WHERE ts < ? AND event_type != 'DEBUG_RULE_NODE' AND event_type != 'DEBUG_RULE_CHAIN'")) { - stmt.setLong(1, otherExpirationTime); - stmt.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(1)); - stmt.execute(); - } catch (SQLException e) { - log.error("SQLException occurred during events TTL task execution ", e); - } - long debugExpirationTime = System.currentTimeMillis() - debugEventsTtl * 1000; - try (Connection connection = dataSource.getConnection(); - PreparedStatement stmt = connection.prepareStatement("DELETE FROM event WHERE ts < ? AND (event_type = 'DEBUG_RULE_NODE' OR event_type = 'DEBUG_RULE_CHAIN')")) { - stmt.setLong(1, debugExpirationTime); - stmt.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(1)); - stmt.execute(); - } catch (SQLException e) { - log.error("SQLException occurred during events TTL task execution ", e); - } - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java deleted file mode 100644 index a85a3eaa12..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright © 2016-2022 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.dao.sql.event; - -import org.springframework.stereotype.Repository; -import org.thingsboard.server.dao.model.sql.EventEntity; -import org.thingsboard.server.dao.util.HsqlDao; - -import javax.persistence.Query; - -@HsqlDao -@Repository -public class HsqlEventInsertRepository extends AbstractEventInsertRepository { - - private static final String P_KEY_CONFLICT_STATEMENT = "(event.id=I.id)"; - private static final String UNQ_KEY_CONFLICT_STATEMENT = "(event.tenant_id=I.tenant_id AND event.entity_type=I.entity_type AND event.entity_id=I.entity_id AND event.event_type=I.event_type AND event.event_uid=I.event_uid)"; - - private static final String INSERT_OR_UPDATE_ON_P_KEY_CONFLICT = getInsertString(P_KEY_CONFLICT_STATEMENT); - private static final String INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT = getInsertString(UNQ_KEY_CONFLICT_STATEMENT); - - @Override - public EventEntity saveOrUpdate(EventEntity entity) { - return saveAndGet(entity, INSERT_OR_UPDATE_ON_P_KEY_CONFLICT, INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT); - } - - @Override - protected EventEntity doProcessSaveOrUpdate(EventEntity entity, String query) { - getQuery(entity, query).executeUpdate(); - return entityManager.find(EventEntity.class, entity.getUuid()); - } - - protected Query getQuery(EventEntity entity, String query) { - return entityManager.createNativeQuery(query, EventEntity.class) - .setParameter("id", entity.getUuid().toString()) - .setParameter("created_time", entity.getCreatedTime()) - .setParameter("body", entity.getBody().toString()) - .setParameter("entity_id", entity.getEntityId().toString()) - .setParameter("entity_type", entity.getEntityType().name()) - .setParameter("event_type", entity.getEventType()) - .setParameter("event_uid", entity.getEventUid()) - .setParameter("tenant_id", entity.getTenantId().toString()) - .setParameter("ts", entity.getTs()); - } - - private static String getInsertString(String conflictStatement) { - return "MERGE INTO event USING (VALUES UUID(:id), :created_time, :body, UUID(:entity_id), :entity_type, :event_type, :event_uid, UUID(:tenant_id), :ts) I (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) ON " + conflictStatement - + " WHEN MATCHED THEN UPDATE SET event.id = I.id, event.created_time = I.created_time, event.body = I.body, event.entity_id = I.entity_id, event.entity_type = I.entity_type, event.event_type = I.event_type, event.event_uid = I.event_uid, event.tenant_id = I.tenant_id, event.ts = I.ts" + - " WHEN NOT MATCHED THEN INSERT (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) VALUES (I.id, I.created_time, I.body, I.entity_id, I.entity_type, I.event_type, I.event_uid, I.tenant_id, I.ts)"; - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index d86a80233e..2b409006d2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -20,8 +20,9 @@ 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.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.event.DebugEvent; @@ -31,18 +32,24 @@ import org.thingsboard.server.common.data.event.LifeCycleEventFilter; import org.thingsboard.server.common.data.event.StatisticsEventFilter; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EventId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.event.EventDao; import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; +import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.Comparator; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.UUID; +import java.util.function.Function; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -70,12 +77,59 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return eventRepository; } + @Autowired + ScheduledLogExecutorComponent logExecutor; + + @Autowired + private StatsFactory statsFactory; + + @Value("${sql.events.batch_size:10000}") + private int batchSize; + + @Value("${sql.events.batch_max_delay:100}") + private long maxDelay; + + @Value("${sql.events.stats_print_interval_ms:10000}") + private long statsPrintIntervalMs; + + @Value("${sql.events.batch_threads:3}") + private int batchThreads; + + @Value("${sql.batch_sort:false}") + private boolean batchSortEnabled; + + private TbSqlBlockingQueueWrapper queue; + + @PostConstruct + private void init() { + TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder() + .logName("Events") + .batchSize(batchSize) + .maxDelay(maxDelay) + .statsPrintIntervalMs(statsPrintIntervalMs) + .statsNamePrefix("events") + .batchSortEnabled(batchSortEnabled) + .build(); + Function hashcodeFunction = entity -> entity.getEntityId().hashCode(); + queue = new TbSqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory); + queue.init(logExecutor, v -> eventInsertRepository.save(v), + Comparator.comparing((EventEntity eventEntity) -> eventEntity.getTs()) + ); + } + + @PreDestroy + private void destroy() { + if (queue != null) { + queue.destroy(); + } + } + @Override - public Event save(TenantId tenantId, Event event) { + public ListenableFuture saveAsync(Event event) { log.debug("Save event [{}] ", event); if (event.getId() == null) { UUID timeBased = Uuids.timeBased(); @@ -92,33 +146,27 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen if (StringUtils.isEmpty(event.getUid())) { event.setUid(event.getId().toString()); } - return save(new EventEntity(event), false).orElse(null); + + return save(new EventEntity(event)); } - @Override - public ListenableFuture saveAsync(Event event) { - log.debug("Save event [{}] ", event); - if (event.getId() == null) { - UUID timeBased = Uuids.timeBased(); - event.setId(new EventId(timeBased)); - event.setCreatedTime(Uuids.unixTimestamp(timeBased)); - } else if (event.getCreatedTime() == 0L) { - UUID eventId = event.getId().getId(); - if (eventId.version() == 1) { - event.setCreatedTime(Uuids.unixTimestamp(eventId)); - } else { - event.setCreatedTime(System.currentTimeMillis()); - } + private ListenableFuture save(EventEntity entity) { + log.debug("Save event [{}] ", entity); + if (entity.getTenantId() == null) { + log.trace("Save system event with predefined id {}", systemTenantId); + entity.setTenantId(systemTenantId); } - if (StringUtils.isEmpty(event.getUid())) { - event.setUid(event.getId().toString()); + if (entity.getUuid() == null) { + entity.setUuid(Uuids.timeBased()); } - return service.submit(() -> save(new EventEntity(event), false).orElse(null)); + if (StringUtils.isEmpty(entity.getEventUid())) { + entity.setEventUid(entity.getUuid().toString()); + } + return addToQueue(entity); } - @Override - public Optional saveIfNotExists(Event event) { - return save(new EventEntity(event), true); + private ListenableFuture addToQueue(EventEntity entity) { + return queue.add(entity); } @Override @@ -259,28 +307,9 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen } @Override - public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { - log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugEventsTtl, otherEventsTtl); - eventCleanupRepository.cleanupEvents(otherEventsTtl, debugEventsTtl); - } - - public Optional save(EventEntity entity, boolean ifNotExists) { - log.debug("Save event [{}] ", entity); - if (entity.getTenantId() == null) { - log.trace("Save system event with predefined id {}", systemTenantId); - entity.setTenantId(systemTenantId); - } - if (entity.getUuid() == null) { - entity.setUuid(Uuids.timeBased()); - } - if (StringUtils.isEmpty(entity.getEventUid())) { - entity.setEventUid(entity.getUuid().toString()); - } - if (ifNotExists && - eventRepository.findByTenantIdAndEntityTypeAndEntityId(entity.getTenantId(), entity.getEntityType(), entity.getEntityId()) != null) { - return Optional.empty(); - } - return Optional.of(DaoUtil.getData(eventInsertRepository.saveOrUpdate(entity))); + public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) { + log.info("Going to cleanup old events. Interval for regular events: [{}:{}], for debug events: [{}:{}]", regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs); + eventCleanupRepository.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs); } private long notNull(Long value) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventCleanupRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventCleanupRepository.java index a50a2e7864..664a148948 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventCleanupRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventCleanupRepository.java @@ -32,12 +32,14 @@ import java.util.concurrent.TimeUnit; public class PsqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository { @Override - public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { + public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) { try (Connection connection = dataSource.getConnection(); - PreparedStatement stmt = connection.prepareStatement("call cleanup_events_by_ttl(?,?,?)")) { - stmt.setLong(1, otherEventsTtl); - stmt.setLong(2, debugEventsTtl); - stmt.setLong(3, 0); + PreparedStatement stmt = connection.prepareStatement("call cleanup_events_by_ttl(?,?,?,?,?)")) { + stmt.setLong(1, regularEventStartTs); + stmt.setLong(2, regularEventEndTs); + stmt.setLong(3, debugEventStartTs); + stmt.setLong(4, debugEventEndTs); + stmt.setLong(5, 0); stmt.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(1)); stmt.execute(); printWarnings(stmt); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java deleted file mode 100644 index 3e7baa571c..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright © 2016-2022 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.dao.sql.event; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Repository; -import org.thingsboard.server.dao.model.sql.EventEntity; -import org.thingsboard.server.dao.util.PsqlDao; - -@Slf4j -@PsqlDao -@Repository -public class PsqlEventInsertRepository extends AbstractEventInsertRepository { - - private static final String P_KEY_CONFLICT_STATEMENT = "(id)"; - private static final String UNQ_KEY_CONFLICT_STATEMENT = "(tenant_id, created_time, entity_type, entity_id, event_type, event_uid)"; - - private static final String UPDATE_P_KEY_STATEMENT = "id = :id"; - private static final String UPDATE_UNQ_KEY_STATEMENT = "created_time = :created_time, tenant_id = :tenant_id, entity_type = :entity_type, entity_id = :entity_id, event_type = :event_type, event_uid = :event_uid"; - - private static final String INSERT_OR_UPDATE_ON_P_KEY_CONFLICT = getInsertOrUpdateString(P_KEY_CONFLICT_STATEMENT, UPDATE_UNQ_KEY_STATEMENT); - private static final String INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT = getInsertOrUpdateString(UNQ_KEY_CONFLICT_STATEMENT, UPDATE_P_KEY_STATEMENT); - - @Override - public EventEntity saveOrUpdate(EventEntity entity) { - return saveAndGet(entity, INSERT_OR_UPDATE_ON_P_KEY_CONFLICT, INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT); - } - - @Override - protected EventEntity doProcessSaveOrUpdate(EventEntity entity, String query) { - return (EventEntity) getQuery(entity, query).getSingleResult(); - - } - - private static String getInsertOrUpdateString(String eventKeyStatement, String updateKeyStatement) { - return "INSERT INTO event (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) " + - "VALUES (:id, :created_time, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id, :ts) " + - "ON CONFLICT " + eventKeyStatement + " DO UPDATE SET body = :body, ts = :ts," + updateKeyStatement + " returning *"; - } -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java index efbbd0c7ae..344f8e97db 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationTemplateDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.dao.DaoUtil; @@ -40,7 +40,7 @@ public class JpaOAuth2ClientRegistrationTemplateDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return repository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java index 58cef17573..e191fc12f4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2Domain; import org.thingsboard.server.dao.DaoUtil; @@ -39,7 +39,7 @@ public class JpaOAuth2DomainDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return repository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java index 4d6f695c67..94fc0a9a17 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; import org.thingsboard.server.dao.DaoUtil; @@ -39,7 +39,7 @@ public class JpaOAuth2MobileDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return repository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java index 7c7209bffb..d18494a015 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2Params; import org.thingsboard.server.dao.model.sql.OAuth2ParamsEntity; @@ -36,7 +36,7 @@ public class JpaOAuth2ParamsDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return repository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java index 158379202f..0a0021bf66 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.oauth2; import lombok.RequiredArgsConstructor; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.PlatformType; @@ -41,7 +41,7 @@ public class JpaOAuth2RegistrationDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return repository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java index 87e01de3e7..89abddc35f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationTemplateRepository.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.dao.sql.oauth2; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationTemplateEntity; import java.util.UUID; -public interface OAuth2ClientRegistrationTemplateRepository extends CrudRepository { +public interface OAuth2ClientRegistrationTemplateRepository extends JpaRepository { OAuth2ClientRegistrationTemplateEntity findByProviderId(String providerId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java index 8ed6040056..f83d0cad94 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java @@ -15,13 +15,13 @@ */ package org.thingsboard.server.dao.sql.oauth2; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.OAuth2DomainEntity; import java.util.List; import java.util.UUID; -public interface OAuth2DomainRepository extends CrudRepository { +public interface OAuth2DomainRepository extends JpaRepository { List findByOauth2ParamsId(UUID oauth2ParamsId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java index 11f9ef8416..cb656a1f7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java @@ -15,13 +15,13 @@ */ package org.thingsboard.server.dao.sql.oauth2; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.OAuth2MobileEntity; import java.util.List; import java.util.UUID; -public interface OAuth2MobileRepository extends CrudRepository { +public interface OAuth2MobileRepository extends JpaRepository { List findByOauth2ParamsId(UUID oauth2ParamsId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java index cd59c0d7e5..e3d14078a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java @@ -15,10 +15,10 @@ */ package org.thingsboard.server.dao.sql.oauth2; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.OAuth2ParamsEntity; import java.util.UUID; -public interface OAuth2ParamsRepository extends CrudRepository { +public interface OAuth2ParamsRepository extends JpaRepository { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java index 19da1ac99e..72a114edaf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.dao.sql.oauth2; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; @@ -24,7 +24,7 @@ import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; import java.util.List; import java.util.UUID; -public interface OAuth2RegistrationRepository extends CrudRepository { +public interface OAuth2RegistrationRepository extends JpaRepository { @Query("SELECT reg " + "FROM OAuth2RegistrationEntity reg " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java index e4504833a3..f6e6dbc5c4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.ota; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.id.TenantId; @@ -40,7 +40,7 @@ public class JpaOtaPackageDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return otaPackageRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java index 4ff37cf873..8ea805332e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.ota; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.ota.OtaPackageType; @@ -47,7 +47,7 @@ public class JpaOtaPackageInfoDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return otaPackageInfoRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java index d216d21bb4..7e770fa5b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java @@ -17,15 +17,15 @@ package org.thingsboard.server.dao.sql.ota; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity; import java.util.UUID; -public interface OtaPackageInfoRepository extends CrudRepository { +public interface OtaPackageInfoRepository extends JpaRepository { @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.tag, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE " + "f.tenantId = :tenantId " + "AND LOWER(f.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageRepository.java index 36d9f04163..1500fa6435 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageRepository.java @@ -15,15 +15,14 @@ */ package org.thingsboard.server.dao.sql.ota; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.OtaPackageEntity; -import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity; import java.util.UUID; -public interface OtaPackageRepository extends CrudRepository { +public interface OtaPackageRepository extends JpaRepository { @Query(value = "SELECT COALESCE(SUM(ota.data_size), 0) FROM ota_package ota WHERE ota.tenant_id = :tenantId AND ota.data IS NOT NULL", nativeQuery = true) Long sumDataSizeByTenantId(@Param("tenantId") UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java index 0469b45442..f879fb21c7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java @@ -17,9 +17,15 @@ package org.thingsboard.server.dao.sql.query; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.SqlParameter; +import org.springframework.jdbc.core.SqlParameterValue; +import org.springframework.jdbc.core.namedparam.NamedParameterUtils; +import org.springframework.jdbc.core.namedparam.ParsedSql; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.stereotype.Component; -import java.util.Arrays; +import java.util.List; +import java.util.UUID; @Component @Slf4j @@ -33,8 +39,72 @@ public class DefaultQueryLogComponent implements QueryLogComponent { @Override public void logQuery(QueryContext ctx, String query, long duration) { if (logSqlQueries && duration > logQueriesThreshold) { - log.info("QUERY: {} took {} ms", query, duration); - Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); + + String sqlToUse = substituteParametersInSqlString(query, ctx); + log.warn("SLOW QUERY took {} ms: {}", duration, sqlToUse); + + } + } + + String substituteParametersInSqlString(String sql, SqlParameterSource paramSource) { + + ParsedSql parsedSql = NamedParameterUtils.parseSqlStatement(sql); + List declaredParams = NamedParameterUtils.buildSqlParameterList(parsedSql, paramSource); + + if (declaredParams.isEmpty()) { + return sql; + } + + for (SqlParameter parSQL: declaredParams) { + String paramName = parSQL.getName(); + if (!paramSource.hasValue(paramName)) { + continue; + } + + Object value = paramSource.getValue(paramName); + if (value instanceof SqlParameterValue) { + value = ((SqlParameterValue)value).getValue(); + } + + if (!(value instanceof Iterable)) { + + String ValueForSQLQuery = getValueForSQLQuery(value); + sql = sql.replace(":" + paramName, ValueForSQLQuery); + continue; + } + + //Iterable + int count = 0; + String valueArrayStr = ""; + + for (Object valueTemp: (Iterable)value) { + + if (count > 0) { + valueArrayStr+=", "; + } + + String valueForSQLQuery = getValueForSQLQuery(valueTemp); + valueArrayStr += valueForSQLQuery; + ++count; + } + + sql = sql.replace(":" + paramName, valueArrayStr); + } + + return sql; + } + + String getValueForSQLQuery(Object valueParameter) { + + if (valueParameter instanceof String) { + return "'" + ((String) valueParameter).replaceAll("'", "''") + "'"; + } + + if (valueParameter instanceof UUID) { + return "'" + valueParameter + "'"; + } + + return String.valueOf(valueParameter); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java index 4e544f35cd..3d68a08f0a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/JpaQueueDao.java @@ -18,7 +18,7 @@ package org.thingsboard.server.dao.sql.queue; import com.google.common.collect.Lists; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -46,7 +46,7 @@ public class JpaQueueDao extends JpaAbstractDao implements Q } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return queueRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueRepository.java index d531072a39..111ca0e4fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/QueueRepository.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.queue; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; @@ -25,7 +26,7 @@ import org.thingsboard.server.dao.model.sql.QueueEntity; import java.util.List; import java.util.UUID; -public interface QueueRepository extends CrudRepository { +public interface QueueRepository extends JpaRepository { QueueEntity findByTenantIdAndTopic(UUID tenantId, String topic); QueueEntity findByTenantIdAndName(UUID tenantId, String name); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java index 507112f8ca..bddd344672 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java @@ -15,26 +15,21 @@ */ package org.thingsboard.server.dao.sql.relation; -import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.dao.model.sql.RelationCompositeKey; import org.thingsboard.server.dao.model.sql.RelationEntity; -import org.thingsboard.server.dao.model.sql.RuleChainEntity; import java.util.List; import java.util.UUID; public interface RelationRepository - extends CrudRepository, JpaSpecificationExecutor { + extends JpaRepository, JpaSpecificationExecutor { List findAllByFromIdAndFromTypeAndRelationTypeGroup(UUID fromId, String fromType, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java index 34a57899d0..dbf60af096 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.resource; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; @@ -48,7 +48,7 @@ public class JpaTbResourceDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return resourceRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java index 3654302def..e2d68a6db4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceInfoDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.id.TenantId; @@ -44,7 +44,7 @@ public class JpaTbResourceInfoDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return resourceInfoRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java index 640b1f95d4..ca4754340b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java @@ -17,14 +17,14 @@ package org.thingsboard.server.dao.sql.resource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.TbResourceInfoEntity; import java.util.UUID; -public interface TbResourceInfoRepository extends CrudRepository { +public interface TbResourceInfoRepository extends JpaRepository { @Query("SELECT tr FROM TbResourceInfoEntity tr WHERE " + "LOWER(tr.title) LIKE LOWER(CONCAT('%', :searchText, '%'))" + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java index a17ba89cb1..165dc9ba26 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java @@ -17,15 +17,15 @@ package org.thingsboard.server.dao.sql.resource; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.TbResourceEntity; import java.util.List; import java.util.UUID; -public interface TbResourceRepository extends CrudRepository { +public interface TbResourceRepository extends JpaRepository { TbResourceEntity findByTenantIdAndResourceTypeAndResourceKey(UUID tenantId, String resourceType, String resourceKey); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java index 3efb2a08c0..bd7188ba74 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.rpc; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -45,7 +45,7 @@ public class JpaRpcDao extends JpaAbstractDao implements RpcDao } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return rpcRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java index 42a473f960..2c0685812d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java @@ -17,15 +17,15 @@ package org.thingsboard.server.dao.sql.rpc; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.dao.model.sql.RpcEntity; import java.util.UUID; -public interface RpcRepository extends CrudRepository { +public interface RpcRepository extends JpaRepository { Page findAllByTenantIdAndDeviceId(UUID tenantId, UUID deviceId, Pageable pageable); Page findAllByTenantIdAndDeviceIdAndStatus(UUID tenantId, UUID deviceId, RpcStatus status, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 43c93eb507..3a7fc921e5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -17,13 +17,12 @@ package org.thingsboard.server.dao.sql.rule; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; 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.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.RuleChainEntity; @@ -31,7 +30,6 @@ import org.thingsboard.server.dao.rule.RuleChainDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.Collection; -import java.util.List; import java.util.Objects; import java.util.UUID; @@ -48,7 +46,7 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return ruleChainRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java index 67c986d3e9..a499e68da8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.rule; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleNode; @@ -42,7 +42,7 @@ public class JpaRuleNodeDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return ruleNodeRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java index c70c0abe62..9b8c7ff8e7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeStateDao.java @@ -17,10 +17,9 @@ package org.thingsboard.server.dao.sql.rule; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleNodeState; @@ -44,7 +43,7 @@ public class JpaRuleNodeStateDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return ruleNodeStateRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index 05eda3bef7..fe9be8dcc7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -17,20 +17,16 @@ package org.thingsboard.server.dao.sql.rule; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChainType; -import org.thingsboard.server.dao.model.sql.RelationEntity; import org.thingsboard.server.dao.model.sql.RuleChainEntity; import java.util.List; import java.util.UUID; -public interface RuleChainRepository extends PagingAndSortingRepository { +public interface RuleChainRepository extends JpaRepository { @Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId = :tenantId " + "AND LOWER(rc.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java index eb42e6f022..b11923a50f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java @@ -15,15 +15,15 @@ */ package org.thingsboard.server.dao.sql.rule; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.RuleNodeEntity; import java.util.List; import java.util.UUID; -public interface RuleNodeRepository extends CrudRepository { +public interface RuleNodeRepository extends JpaRepository { @Query("SELECT r FROM RuleNodeEntity r WHERE r.ruleChainId in " + "(select id from RuleChainEntity rc WHERE rc.tenantId = :tenantId) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java index 7da7641500..1fb243336b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeStateRepository.java @@ -17,16 +17,14 @@ package org.thingsboard.server.dao.sql.rule; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.model.sql.RuleNodeStateEntity; import java.util.UUID; -public interface RuleNodeStateRepository extends PagingAndSortingRepository { +public interface RuleNodeStateRepository extends JpaRepository { @Query("SELECT e FROM RuleNodeStateEntity e WHERE e.ruleNodeId = :ruleNodeId") Page findByRuleNodeId(@Param("ruleNodeId") UUID ruleNodeId, Pageable pageable); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java index d2bb1ab062..58423645b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.sql.settings; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.AdminSettingsEntity; import java.util.UUID; @@ -23,7 +23,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/6/2017. */ -public interface AdminSettingsRepository extends CrudRepository { +public interface AdminSettingsRepository extends JpaRepository { AdminSettingsEntity findByKey(String key); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java index d1ca43be92..438274e3cc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java @@ -17,7 +17,7 @@ package org.thingsboard.server.dao.sql.settings; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.TenantId; @@ -41,7 +41,7 @@ public class JpaAdminSettingsDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return adminSettingsRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java index ea4060aa44..a98c5ab39c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.tenant; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; @@ -51,7 +51,7 @@ public class JpaTenantDao extends JpaAbstractSearchTextDao } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return tenantRepository; } @@ -61,19 +61,17 @@ public class JpaTenantDao extends JpaAbstractSearchTextDao } @Override - public PageData findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink) { + public PageData findTenants(TenantId tenantId, PageLink pageLink) { return DaoUtil.toPageData(tenantRepository - .findByRegionNextPage( - region, + .findTenantsNextPage( Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); } @Override - public PageData findTenantInfosByRegion(TenantId tenantId, String region, PageLink pageLink) { + public PageData findTenantInfos(TenantId tenantId, PageLink pageLink) { return DaoUtil.toPageData(tenantRepository - .findTenantInfoByRegionNextPage( - region, + .findTenantInfosNextPage( Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink, TenantInfoEntity.tenantInfoColumnMap))); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantProfileDao.java index 8b17123866..3cad31f553 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantProfileDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.tenant; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; @@ -43,7 +43,7 @@ public class JpaTenantProfileDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return tenantProfileRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java index f08cb65983..5241148764 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java @@ -17,15 +17,15 @@ package org.thingsboard.server.dao.sql.tenant; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.dao.model.sql.TenantProfileEntity; import java.util.UUID; -public interface TenantProfileRepository extends PagingAndSortingRepository { +public interface TenantProfileRepository extends JpaRepository { @Query("SELECT new org.thingsboard.server.common.data.EntityInfo(t.id, 'TENANT_PROFILE', t.name) " + "FROM TenantProfileEntity t " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java index 8aa86a535a..7042dcbb16 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.tenant; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.TenantEntity; import org.thingsboard.server.dao.model.sql.TenantInfoEntity; @@ -29,7 +29,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 4/30/2017. */ -public interface TenantRepository extends PagingAndSortingRepository { +public interface TenantRepository extends JpaRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.TenantInfoEntity(t, p.name) " + "FROM TenantEntity t " + @@ -37,19 +37,15 @@ public interface TenantRepository extends PagingAndSortingRepository findByRegionNextPage(@Param("region") String region, - @Param("textSearch") String textSearch, - Pageable pageable); + @Query("SELECT t FROM TenantEntity t WHERE LOWER(t.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") + Page findTenantsNextPage(@Param("textSearch") String textSearch, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.TenantInfoEntity(t, p.name) " + "FROM TenantEntity t " + "LEFT JOIN TenantProfileEntity p on p.id = t.tenantProfileId " + - "WHERE t.region = :region " + - "AND LOWER(t.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") - Page findTenantInfoByRegionNextPage(@Param("region") String region, - @Param("textSearch") String textSearch, + "WHERE LOWER(t.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") + Page findTenantInfosNextPage(@Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT t.id FROM TenantEntity t") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java index cb190d8417..7b060e0b43 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.dao.sql.usagerecord; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.ApiUsageStateEntity; @@ -27,7 +27,7 @@ import java.util.UUID; /** * @author Valerii Sosliuk */ -public interface ApiUsageStateRepository extends CrudRepository { +public interface ApiUsageStateRepository extends JpaRepository { @Query("SELECT ur FROM ApiUsageStateEntity ur WHERE ur.tenantId = :tenantId " + "AND ur.entityId = :tenantId AND ur.entityType = 'TENANT' ") diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java index 13f8d605e8..7c6801042d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.sql.usagerecord; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.EntityId; @@ -45,7 +45,7 @@ public class JpaApiUsageStateDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return apiUsageStateRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java index 676745c1cb..69f83efc86 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.user; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; @@ -42,7 +42,7 @@ public class JpaUserCredentialsDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return userCredentialsRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index 95e6b1e3cd..6b2de9872a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.user; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.TenantId; @@ -48,7 +48,7 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple } @Override - protected CrudRepository getCrudRepository() { + protected JpaRepository getRepository() { return userRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java index 22f1a75e94..c55a2962c9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.sql.user; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sql.UserCredentialsEntity; import java.util.UUID; @@ -23,7 +23,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 4/22/2017. */ -public interface UserCredentialsRepository extends CrudRepository { +public interface UserCredentialsRepository extends JpaRepository { UserCredentialsEntity findByUserId(UUID userId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index fa9bc3eebb..51f6138cc1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.user; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.sql.UserEntity; @@ -28,7 +28,7 @@ import java.util.UUID; /** * @author Valerii Sosliuk */ -public interface UserRepository extends PagingAndSortingRepository { +public interface UserRepository extends JpaRepository { UserEntity findByEmail(String email); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java index 44ca9dd511..bbaf2529a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.widget; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.widget.WidgetType; @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.WidgetTypeDetailsEntity; -import org.thingsboard.server.dao.model.sql.WidgetTypeEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.widget.WidgetTypeDao; @@ -46,7 +45,7 @@ public class JpaWidgetTypeDao extends JpaAbstractDao getCrudRepository() { + protected JpaRepository getRepository() { return widgetTypeRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java index 105f01cc5f..6b42561bce 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDao.java @@ -16,7 +16,7 @@ package org.thingsboard.server.dao.sql.widget; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -47,7 +47,7 @@ public class JpaWidgetsBundleDao extends JpaAbstractSearchTextDao getCrudRepository() { + protected JpaRepository getRepository() { return widgetsBundleRepository; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java index 5ae64e6aa9..cc9ab0ac2e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.dao.sql.widget; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.WidgetTypeDetailsEntity; import org.thingsboard.server.dao.model.sql.WidgetTypeEntity; @@ -25,7 +25,7 @@ import org.thingsboard.server.dao.model.sql.WidgetTypeInfoEntity; import java.util.List; import java.util.UUID; -public interface WidgetTypeRepository extends CrudRepository { +public interface WidgetTypeRepository extends JpaRepository { @Query("SELECT wt FROM WidgetTypeEntity wt WHERE wt.id = :widgetTypeId") WidgetTypeEntity findWidgetTypeById(@Param("widgetTypeId") UUID widgetTypeId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java index 36a02743e3..0de8d0220b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java @@ -17,8 +17,8 @@ package org.thingsboard.server.dao.sql.widget; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sql.WidgetsBundleEntity; @@ -27,7 +27,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 4/23/2017. */ -public interface WidgetsBundleRepository extends PagingAndSortingRepository { +public interface WidgetsBundleRepository extends JpaRepository { WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java index 45f7066ade..46ff911bf6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/dictionary/TsKvDictionaryRepository.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.dictionary; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.JpaRepository; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; import org.thingsboard.server.dao.util.SqlTsOrTsLatestAnyDao; @@ -23,7 +23,7 @@ import org.thingsboard.server.dao.util.SqlTsOrTsLatestAnyDao; import java.util.Optional; @SqlTsOrTsLatestAnyDao -public interface TsKvDictionaryRepository extends CrudRepository { +public interface TsKvDictionaryRepository extends JpaRepository { Optional findByKeyId(int keyId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java index 12257f1b88..bf5009b53d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.dao.sqlts.latest; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey; import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; @@ -24,7 +24,7 @@ import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity; import java.util.List; import java.util.UUID; -public interface TsKvLatestRepository extends CrudRepository { +public interface TsKvLatestRepository extends JpaRepository { @Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " + "INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java index 0dff32b203..075cffcbc7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java @@ -16,9 +16,9 @@ package org.thingsboard.server.dao.sqlts.timescale; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvCompositeKey; @@ -29,7 +29,7 @@ import java.util.List; import java.util.UUID; @TimescaleDBTsOrTsLatestDao -public interface TsKvTimescaleRepository extends CrudRepository { +public interface TsKvTimescaleRepository extends JpaRepository { @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + "AND tskv.key = :entityKey " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java index 758667fedb..c603d9d92b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java @@ -16,9 +16,9 @@ package org.thingsboard.server.dao.sqlts.ts; import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.scheduling.annotation.Async; import org.springframework.transaction.annotation.Transactional; @@ -29,7 +29,7 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; -public interface TsKvRepository extends CrudRepository { +public interface TsKvRepository extends JpaRepository { @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java index e08ed4745f..1d7461083b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java @@ -39,15 +39,14 @@ public interface TenantDao extends Dao { Tenant save(TenantId tenantId, Tenant tenant); /** - * Find tenants by region and page link. + * Find tenants by page link. * - * @param region the region * @param pageLink the page link * @return the list of tenant objects */ - PageData findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink); + PageData findTenants(TenantId tenantId, PageLink pageLink); - PageData findTenantInfosByRegion(TenantId tenantId, String region, PageLink pageLink); + PageData findTenantInfos(TenantId tenantId, PageLink pageLink); PageData findTenantsIds(PageLink pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 2b8611846a..0976c49410 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -16,7 +16,6 @@ package org.thingsboard.server.dao.tenant; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; @@ -29,12 +28,8 @@ import org.thingsboard.server.common.data.id.TenantId; 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.queue.ProcessingStrategy; -import org.thingsboard.server.common.data.queue.SubmitStrategy; -import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; @@ -43,10 +38,6 @@ import org.thingsboard.server.dao.service.Validator; import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; import static org.thingsboard.server.common.data.CacheConstants.TENANT_PROFILE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -63,6 +54,9 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T @Autowired private CacheManager cacheManager; + @Autowired + private DataValidator tenantProfileValidator; + @Cacheable(cacheNames = TENANT_PROFILE_CACHE, key = "{#tenantProfileId.id}") @Override public TenantProfile findTenantProfileById(TenantId tenantId, TenantProfileId tenantProfileId) { @@ -112,10 +106,10 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T if (tenantProfile != null && tenantProfile.isDefault()) { throw new DataValidationException("Deletion of Default Tenant Profile is prohibited!"); } - this.removeTenantProfile(tenantId, tenantProfileId); + this.removeTenantProfile(tenantId, tenantProfileId, false); } - private void removeTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId) { + private void removeTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId, boolean isDefault) { try { tenantProfileDao.removeById(tenantId, tenantProfileId.getId()); } catch (Exception t) { @@ -130,6 +124,10 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T Cache cache = cacheManager.getCache(TENANT_PROFILE_CACHE); cache.evict(Collections.singletonList(tenantProfileId.getId())); cache.evict(Arrays.asList("info", tenantProfileId.getId())); + if (isDefault) { + cache.evict(Collections.singletonList("default")); + cache.evict(Arrays.asList("default", "info")); + } } @Override @@ -217,118 +215,7 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T tenantProfilesRemover.removeEntities(tenantId, null); } - private DataValidator tenantProfileValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId tenantId, TenantProfile tenantProfile) { - if (StringUtils.isEmpty(tenantProfile.getName())) { - throw new DataValidationException("Tenant profile name should be specified!"); - } - if (tenantProfile.getProfileData() == null) { - throw new DataValidationException("Tenant profile data should be specified!"); - } - if (tenantProfile.getProfileData().getConfiguration() == null) { - throw new DataValidationException("Tenant profile data configuration should be specified!"); - } - if (tenantProfile.isDefault()) { - TenantProfile defaultTenantProfile = findDefaultTenantProfile(tenantId); - if (defaultTenantProfile != null && !defaultTenantProfile.getId().equals(tenantProfile.getId())) { - throw new DataValidationException("Another default tenant profile is present!"); - } - } - - if (tenantProfile.isIsolatedTbRuleEngine()) { - List queueConfiguration = tenantProfile.getProfileData().getQueueConfiguration(); - if (queueConfiguration == null) { - throw new DataValidationException("Tenant profile data queue configuration should be specified!"); - } - - Optional mainQueueConfig = - queueConfiguration - .stream() - .filter(q -> q.getName().equals("Main")) - .findAny(); - if (mainQueueConfig.isEmpty()) { - throw new DataValidationException("Main queue configuration should be specified!"); - } - - queueConfiguration.forEach(this::validateQueueConfiguration); - - Set queueNames = new HashSet<>(queueConfiguration.size()); - - queueConfiguration.forEach(q -> { - String name = q.getName(); - if (queueNames.contains(name)) { - throw new DataValidationException(String.format("Queue configuration name '%s' already present!", name)); - } else { - queueNames.add(name); - } - }); - } - } - - @Override - protected void validateUpdate(TenantId tenantId, TenantProfile tenantProfile) { - TenantProfile old = tenantProfileDao.findById(TenantId.SYS_TENANT_ID, tenantProfile.getId().getId()); - if (old == null) { - throw new DataValidationException("Can't update non existing tenant profile!"); - } else if (old.isIsolatedTbRuleEngine() != tenantProfile.isIsolatedTbRuleEngine()) { - throw new DataValidationException("Can't update isolatedTbRuleEngine property!"); - } else if (old.isIsolatedTbCore() != tenantProfile.isIsolatedTbCore()) { - throw new DataValidationException("Can't update isolatedTbCore property!"); - } - } - - private void validateQueueConfiguration(TenantProfileQueueConfiguration queue) { - if (StringUtils.isEmpty(queue.getName())) { - throw new DataValidationException("Queue name should be specified!"); - } - if (StringUtils.isBlank(queue.getTopic())) { - throw new DataValidationException("Queue topic should be non empty and without spaces!"); - } - if (queue.getPollInterval() < 1) { - throw new DataValidationException("Queue poll interval should be more then 0!"); - } - if (queue.getPartitions() < 1) { - throw new DataValidationException("Queue partitions should be more then 0!"); - } - if (queue.getPackProcessingTimeout() < 1) { - throw new DataValidationException("Queue pack processing timeout should be more then 0!"); - } - - SubmitStrategy submitStrategy = queue.getSubmitStrategy(); - if (submitStrategy == null) { - throw new DataValidationException("Queue submit strategy can't be null!"); - } - if (submitStrategy.getType() == null) { - throw new DataValidationException("Queue submit strategy type can't be null!"); - } - if (submitStrategy.getType() == SubmitStrategyType.BATCH && submitStrategy.getBatchSize() < 1) { - throw new DataValidationException("Queue submit strategy batch size should be more then 0!"); - } - ProcessingStrategy processingStrategy = queue.getProcessingStrategy(); - if (processingStrategy == null) { - throw new DataValidationException("Queue processing strategy can't be null!"); - } - if (processingStrategy.getType() == null) { - throw new DataValidationException("Queue processing strategy type can't be null!"); - } - if (processingStrategy.getRetries() < 0) { - throw new DataValidationException("Queue processing strategy retries can't be less then 0!"); - } - if (processingStrategy.getFailurePercentage() < 0 || processingStrategy.getFailurePercentage() > 100) { - throw new DataValidationException("Queue processing strategy failure percentage should be in a range from 0 to 100!"); - } - if (processingStrategy.getPauseBetweenRetries() < 0) { - throw new DataValidationException("Queue processing strategy pause between retries can't be less then 0!"); - } - if (processingStrategy.getMaxPauseBetweenRetries() < processingStrategy.getPauseBetweenRetries()) { - throw new DataValidationException("Queue processing strategy MAX pause between retries can't be less then pause between retries!"); - } - } - }; - - private PaginatedRemover tenantProfilesRemover = + private final PaginatedRemover tenantProfilesRemover = new PaginatedRemover() { @Override @@ -338,7 +225,7 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T @Override protected void removeEntity(TenantId tenantId, TenantProfile entity) { - removeTenantProfile(tenantId, entity.getId()); + removeTenantProfile(tenantId, entity.getId(), entity.isDefault()); } }; diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 187c87f1d3..61c037e2d6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -17,9 +17,7 @@ package org.thingsboard.server.dao.tenant; 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.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; @@ -35,7 +33,6 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.resource.ResourceService; @@ -59,9 +56,6 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe private static final String DEFAULT_TENANT_REGION = "Global"; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - @Value("${zk.enabled}") - private Boolean zkEnabled; - @Autowired private TenantDao tenantDao; @@ -107,6 +101,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private RpcService rpcService; + @Autowired + private DataValidator tenantValidator; + @Autowired private QueueService queueService; @@ -175,14 +172,14 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe public PageData findTenants(PageLink pageLink) { log.trace("Executing findTenants pageLink [{}]", pageLink); Validator.validatePageLink(pageLink); - return tenantDao.findTenantsByRegion(TenantId.SYS_TENANT_ID, DEFAULT_TENANT_REGION, pageLink); + return tenantDao.findTenants(TenantId.SYS_TENANT_ID, pageLink); } @Override public PageData findTenantInfos(PageLink pageLink) { log.trace("Executing findTenantInfos pageLink [{}]", pageLink); Validator.validatePageLink(pageLink); - return tenantDao.findTenantInfosByRegion(TenantId.SYS_TENANT_ID, DEFAULT_TENANT_REGION, pageLink); + return tenantDao.findTenantInfos(TenantId.SYS_TENANT_ID, pageLink); } @Override @@ -194,45 +191,15 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Override public void deleteTenants() { log.trace("Executing deleteTenants"); - tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, DEFAULT_TENANT_REGION); + tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID); } - private DataValidator tenantValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId tenantId, Tenant tenant) { - if (StringUtils.isEmpty(tenant.getTitle())) { - throw new DataValidationException("Tenant title should be specified!"); - } - if (!StringUtils.isEmpty(tenant.getEmail())) { - validateEmail(tenant.getEmail()); - } - validateTenantProfile(tenantId, tenant); - } - - @Override - protected void validateUpdate(TenantId tenantId, Tenant tenant) { - Tenant old = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId.getId()); - if (old == null) { - throw new DataValidationException("Can't update non existing tenant!"); - } - validateTenantProfile(tenantId, tenant); - } - - private void validateTenantProfile(TenantId tenantId, Tenant tenant) { - TenantProfile tenantProfileById = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId()); - if (!zkEnabled && (tenantProfileById.isIsolatedTbCore() || tenantProfileById.isIsolatedTbRuleEngine())) { -// throw new DataValidationException("Can't use isolated tenant profiles in monolith setup!"); - } - } - }; - - private PaginatedRemover tenantsRemover = - new PaginatedRemover() { + private PaginatedRemover tenantsRemover = + new PaginatedRemover<>() { @Override - protected PageData findEntities(TenantId tenantId, String region, PageLink pageLink) { - return tenantDao.findTenantsByRegion(tenantId, region, pageLink); + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { + return tenantDao.findTenants(tenantId, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 5b75342420..2193305837 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.usagerecord; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiFeature; @@ -33,7 +34,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantProfileDao; @@ -47,6 +47,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @Service @Slf4j +@AllArgsConstructor public class ApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @@ -54,13 +55,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A private final TenantProfileDao tenantProfileDao; private final TenantDao tenantDao; private final TimeseriesService tsService; - - public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao, TimeseriesService tsService) { - this.tenantDao = tenantDao; - this.apiUsageStateDao = apiUsageStateDao; - this.tenantProfileDao = tenantProfileDao; - this.tsService = tsService; - } + private final DataValidator apiUsageStateValidator; @Override public void deleteApiUsageStateByTenantId(TenantId tenantId) { @@ -157,24 +152,4 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A return apiUsageStateDao.findById(tenantId, id.getId()); } - private DataValidator apiUsageStateValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) { - if (apiUsageState.getTenantId() == null) { - throw new DataValidationException("ApiUsageState should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId()); - if (tenant == null && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) { - throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!"); - } - } - if (apiUsageState.getEntityId() == null) { - throw new DataValidationException("UsageRecord should be assigned to entity!"); - } else if (apiUsageState.getEntityId().getEntityType() != EntityType.TENANT && apiUsageState.getEntityId().getEntityType() != EntityType.CUSTOMER) { - throw new DataValidationException("Only Tenant and Customer Usage Records are supported!"); - } - } - }; - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index d48520648e..ba0f09d64e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -19,19 +19,15 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.DisabledException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -39,19 +35,12 @@ import org.thingsboard.server.common.data.id.UserCredentialsId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; import java.util.HashMap; import java.util.Map; @@ -80,22 +69,19 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private final UserDao userDao; private final UserCredentialsDao userCredentialsDao; - private final TenantDao tenantDao; - private final CustomerDao customerDao; - private final TbTenantProfileCache tenantProfileCache; + private final DataValidator userValidator; + private final DataValidator userCredentialsValidator; private final ApplicationEventPublisher eventPublisher; public UserServiceImpl(UserDao userDao, UserCredentialsDao userCredentialsDao, - TenantDao tenantDao, - CustomerDao customerDao, - @Lazy TbTenantProfileCache tenantProfileCache, + DataValidator userValidator, + DataValidator userCredentialsValidator, ApplicationEventPublisher eventPublisher) { this.userDao = userDao; this.userCredentialsDao = userCredentialsDao; - this.tenantDao = tenantDao; - this.customerDao = customerDao; - this.tenantProfileCache = tenantProfileCache; + this.userValidator = userValidator; + this.userCredentialsValidator = userCredentialsValidator; this.eventPublisher = eventPublisher; } @@ -383,119 +369,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic saveUser(user); } - private final DataValidator userValidator = - new DataValidator<>() { - @Override - protected void validateCreate(TenantId tenantId, User user) { - if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); - long maxUsers = profileConfiguration.getMaxUsers(); - validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER); - } - } - - @Override - protected void validateDataImpl(TenantId requestTenantId, User user) { - if (StringUtils.isEmpty(user.getEmail())) { - throw new DataValidationException("User email should be specified!"); - } - - validateEmail(user.getEmail()); - - Authority authority = user.getAuthority(); - if (authority == null) { - throw new DataValidationException("User authority isn't defined!"); - } - TenantId tenantId = user.getTenantId(); - if (tenantId == null) { - tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID); - user.setTenantId(tenantId); - } - CustomerId customerId = user.getCustomerId(); - if (customerId == null) { - customerId = new CustomerId(ModelConstants.NULL_UUID); - user.setCustomerId(customerId); - } - - switch (authority) { - case SYS_ADMIN: - if (!tenantId.getId().equals(ModelConstants.NULL_UUID) - || !customerId.getId().equals(ModelConstants.NULL_UUID)) { - throw new DataValidationException("System administrator can't be assigned neither to tenant nor to customer!"); - } - break; - case TENANT_ADMIN: - if (tenantId.getId().equals(ModelConstants.NULL_UUID)) { - throw new DataValidationException("Tenant administrator should be assigned to tenant!"); - } else if (!customerId.getId().equals(ModelConstants.NULL_UUID)) { - throw new DataValidationException("Tenant administrator can't be assigned to customer!"); - } - break; - case CUSTOMER_USER: - if (tenantId.getId().equals(ModelConstants.NULL_UUID) - || customerId.getId().equals(ModelConstants.NULL_UUID)) { - throw new DataValidationException("Customer user should be assigned to customer!"); - } - break; - default: - break; - } - - User existentUserWithEmail = findUserByEmail(tenantId, user.getEmail()); - if (existentUserWithEmail != null && !isSameData(existentUserWithEmail, user)) { - throw new DataValidationException("User with email '" + user.getEmail() + "' " - + " already present in database!"); - } - if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) { - Tenant tenant = tenantDao.findById(tenantId, user.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("User is referencing to non-existent tenant!"); - } - } - if (!customerId.getId().equals(ModelConstants.NULL_UUID)) { - Customer customer = customerDao.findById(tenantId, user.getCustomerId().getId()); - if (customer == null) { - throw new DataValidationException("User is referencing to non-existent customer!"); - } else if (!customer.getTenantId().getId().equals(tenantId.getId())) { - throw new DataValidationException("User can't be assigned to customer from different tenant!"); - } - } - } - }; - - private final DataValidator userCredentialsValidator = - new DataValidator<>() { - - @Override - protected void validateCreate(TenantId tenantId, UserCredentials userCredentials) { - throw new IncorrectParameterException("Creation of new user credentials is prohibited."); - } - - @Override - protected void validateDataImpl(TenantId tenantId, UserCredentials userCredentials) { - if (userCredentials.getUserId() == null) { - throw new DataValidationException("User credentials should be assigned to user!"); - } - if (userCredentials.isEnabled()) { - if (StringUtils.isEmpty(userCredentials.getPassword())) { - throw new DataValidationException("Enabled user credentials should have password!"); - } - if (StringUtils.isNotEmpty(userCredentials.getActivateToken())) { - throw new DataValidationException("Enabled user credentials can't have activate token!"); - } - } - UserCredentials existingUserCredentialsEntity = userCredentialsDao.findById(tenantId, userCredentials.getId().getId()); - if (existingUserCredentialsEntity == null) { - throw new DataValidationException("Unable to update non-existent user credentials!"); - } - User user = findUserById(tenantId, userCredentials.getUserId()); - if (user == null) { - throw new DataValidationException("Can't assign user credentials to non-existent user!"); - } - } - }; - private final PaginatedRemover tenantAdminsRemover = new PaginatedRemover<>() { @Override protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java index 4defd1b34d..9b64dc0d68 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java @@ -16,21 +16,15 @@ package org.thingsboard.server.dao.widget; 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.Tenant; 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.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TenantDao; import java.util.List; @@ -44,10 +38,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { private WidgetTypeDao widgetTypeDao; @Autowired - private TenantDao tenantDao; - - @Autowired - private WidgetsBundleDao widgetsBundleService; + private DataValidator widgetTypeValidator; @Override public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) { @@ -121,64 +112,4 @@ public class WidgetTypeServiceImpl implements WidgetTypeService { } } - private DataValidator widgetTypeValidator = - new DataValidator<>() { - @Override - protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { - if (StringUtils.isEmpty(widgetTypeDetails.getName())) { - throw new DataValidationException("Widgets type name should be specified!"); - } - if (StringUtils.isEmpty(widgetTypeDetails.getBundleAlias())) { - throw new DataValidationException("Widgets type bundle alias should be specified!"); - } - if (widgetTypeDetails.getDescriptor() == null || widgetTypeDetails.getDescriptor().size() == 0) { - throw new DataValidationException("Widgets type descriptor can't be empty!"); - } - if (widgetTypeDetails.getTenantId() == null) { - widgetTypeDetails.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID)); - } - if (!widgetTypeDetails.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { - Tenant tenant = tenantDao.findById(tenantId, widgetTypeDetails.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Widget type is referencing to non-existent tenant!"); - } - } - } - - @Override - protected void validateCreate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias()); - if (widgetsBundle == null) { - throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!"); - } - String alias = widgetTypeDetails.getAlias(); - if (alias == null || alias.trim().isEmpty()) { - alias = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_"); - } - String originalAlias = alias; - int c = 1; - WidgetType withSameAlias; - do { - withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias(), alias); - if (withSameAlias != null) { - alias = originalAlias + (++c); - } - } while (withSameAlias != null); - widgetTypeDetails.setAlias(alias); - } - - @Override - protected void validateUpdate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { - WidgetType storedWidgetType = widgetTypeDao.findById(tenantId, widgetTypeDetails.getId().getId()); - if (!storedWidgetType.getTenantId().getId().equals(widgetTypeDetails.getTenantId().getId())) { - throw new DataValidationException("Can't move existing widget type to different tenant!"); - } - if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) { - throw new DataValidationException("Update of widget type bundle alias is prohibited!"); - } - if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) { - throw new DataValidationException("Update of widget type alias is prohibited!"); - } - } - }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java index 96e18abd48..df5e4a29f1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java @@ -16,22 +16,17 @@ package org.thingsboard.server.dao.widget; 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.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; import java.util.List; @@ -48,10 +43,10 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { private WidgetsBundleDao widgetsBundleDao; @Autowired - private TenantDao tenantDao; + private WidgetTypeService widgetTypeService; @Autowired - private WidgetTypeService widgetTypeService; + private DataValidator widgetsBundleValidator; @Override public WidgetsBundle findWidgetsBundleById(TenantId tenantId, WidgetsBundleId widgetsBundleId) { @@ -150,56 +145,6 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { tenantWidgetsBundleRemover.removeEntities(tenantId, tenantId); } - private DataValidator widgetsBundleValidator = - new DataValidator() { - - @Override - protected void validateDataImpl(TenantId tenantId, WidgetsBundle widgetsBundle) { - if (StringUtils.isEmpty(widgetsBundle.getTitle())) { - throw new DataValidationException("Widgets bundle title should be specified!"); - } - if (widgetsBundle.getTenantId() == null) { - widgetsBundle.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID)); - } - if (!widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { - Tenant tenant = tenantDao.findById(tenantId, widgetsBundle.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!"); - } - } - } - - @Override - protected void validateCreate(TenantId tenantId, WidgetsBundle widgetsBundle) { - String alias = widgetsBundle.getAlias(); - if (alias == null || alias.trim().isEmpty()) { - alias = widgetsBundle.getTitle().toLowerCase().replaceAll("\\W+", "_"); - } - String originalAlias = alias; - int c = 1; - WidgetsBundle withSameAlias; - do { - withSameAlias = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetsBundle.getTenantId().getId(), alias); - if (withSameAlias != null) { - alias = originalAlias + (++c); - } - } while(withSameAlias != null); - widgetsBundle.setAlias(alias); - } - - @Override - protected void validateUpdate(TenantId tenantId, WidgetsBundle widgetsBundle) { - WidgetsBundle storedWidgetsBundle = widgetsBundleDao.findById(tenantId, widgetsBundle.getId().getId()); - if (!storedWidgetsBundle.getTenantId().getId().equals(widgetsBundle.getTenantId().getId())) { - throw new DataValidationException("Can't move existing widgets bundle to different tenant!"); - } - if (!storedWidgetsBundle.getAlias().equals(widgetsBundle.getAlias())) { - throw new DataValidationException("Update of widgets bundle alias is prohibited!"); - } - } - - }; - private PaginatedRemover tenantWidgetsBundleRemover = new PaginatedRemover() { diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 1f3717429b..d71e2fb4dd 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -213,7 +213,6 @@ CREATE TABLE IF NOT EXISTS ota_package ( additional_info varchar, search_text varchar(255), CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) --- CONSTRAINT fk_device_profile_firmware FOREIGN KEY (device_profile_id) REFERENCES device_profile(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS queue( @@ -258,6 +257,18 @@ CREATE TABLE IF NOT EXISTS device_profile ( CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id) ); +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_device_profile_ota_package') THEN + ALTER TABLE ota_package + ADD CONSTRAINT fk_device_profile_ota_package + FOREIGN KEY (device_profile_id) REFERENCES device_profile (id) + ON DELETE CASCADE; + END IF; + END; +$$; + -- We will use one-to-many relation in the first release and extend this feature in case of user requests -- CREATE TABLE IF NOT EXISTS device_profile_firmware ( -- device_profile_id uuid NOT NULL, @@ -647,24 +658,31 @@ CREATE TABLE IF NOT EXISTS rpc ( status varchar(255) NOT NULL ); -CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) +CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl( + IN regular_events_start_ts bigint, + IN regular_events_end_ts bigint, + IN debug_events_start_ts bigint, + IN debug_events_end_ts bigint, + INOUT deleted bigint) LANGUAGE plpgsql AS $$ DECLARE - ttl_ts bigint; - debug_ttl_ts bigint; ttl_deleted_count bigint DEFAULT 0; debug_ttl_deleted_count bigint DEFAULT 0; BEGIN - IF ttl > 0 THEN - ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint; + IF regular_events_start_ts > 0 AND regular_events_end_ts > 0 THEN EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type != %L::varchar AND event_type != %L::varchar) RETURNING *) SELECT count(*) FROM deleted', ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count; + 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || + '(event_type != %L::varchar AND event_type != %L::varchar)) RETURNING *) ' || + 'SELECT count(*) FROM deleted', regular_events_start_ts, regular_events_end_ts, + 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count; END IF; - IF debug_ttl > 0 THEN - debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint; + IF debug_events_start_ts > 0 AND debug_events_end_ts > 0 THEN EXECUTE format( - 'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type = %L::varchar OR event_type = %L::varchar) RETURNING *) SELECT count(*) FROM deleted', debug_ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count; + 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' || + '(event_type = %L::varchar OR event_type = %L::varchar)) RETURNING *) ' || + 'SELECT count(*) FROM deleted', debug_events_start_ts, debug_events_end_ts, + 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count; END IF; RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count; RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count; diff --git a/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java index 7af91b3c85..5f7fc7caa0 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java @@ -16,10 +16,10 @@ package org.thingsboard.server.dao; import org.junit.runner.RunWith; +import org.mockito.Answers; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; -import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; @@ -35,9 +35,9 @@ import org.thingsboard.server.dao.service.DaoSqlTest; @TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class}) -public abstract class AbstractJpaDaoTest extends AbstractTransactionalJUnit4SpringContextTests { +public abstract class AbstractJpaDaoTest { - @MockBean + @MockBean(answer = Answers.RETURNS_MOCKS) StatsFactory statsFactory; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java index e8cf4b3e72..fccbc705b2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/JpaDaoTestSuite.java @@ -21,12 +21,7 @@ import org.junit.runner.RunWith; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.sql.tenant.*Test", - "org.thingsboard.server.dao.sql.component.*Test", - "org.thingsboard.server.dao.sql.customer.*Test", - "org.thingsboard.server.dao.sql.dashboard.*Test", - "org.thingsboard.server.dao.sql.query.*Test", - "org.thingsboard.server.dao.sql.*THIS_MUST_BE_FIXED_Test", + "org.thingsboard.server.dao.sql.*Test" }) public class JpaDaoTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index bc7abf8342..12f77ece14 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -25,6 +25,7 @@ import org.junit.runner.RunWith; "org.thingsboard.server.dao.service.event.sql.*SqlTest", "org.thingsboard.server.dao.service.sql.*SqlTest", "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest", + "org.thingsboard.server.dao.service.install.sql.*SqlTest" }) public class SqlDaoServiceTestSuite { } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index dae497b52d..e3cdfefe01 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -33,14 +33,18 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.audit.AuditLogLevelFilter; @@ -71,6 +75,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import java.io.IOException; +import java.nio.ByteBuffer; import java.util.Comparator; import java.util.HashMap; import java.util.Map; @@ -255,4 +260,21 @@ public abstract class AbstractServiceTest { edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); return edge; } + + protected OtaPackage constructDefaultOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId) { + OtaPackage firmware = new OtaPackage(); + firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(OtaPackageType.FIRMWARE); + firmware.setTitle("My firmware"); + firmware.setVersion("3.3.3"); + firmware.setFileName("filename.txt"); + firmware.setContentType("text/plain"); + firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); + firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); + firmware.setData(ByteBuffer.wrap(new byte[]{1})); + firmware.setDataSize(1L); + return firmware; + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java index ee1d50ecc5..4dd3169579 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java @@ -16,11 +16,16 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; @@ -29,17 +34,22 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; public abstract class BaseCustomerServiceTest extends AbstractServiceTest { + static final int TIMEOUT = 30; - private IdComparator idComparator = new IdComparator<>(); + ListeningExecutorService executor; private TenantId tenantId; @Before public void before() { + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); + Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); Tenant savedTenant = tenantService.saveTenant(tenant); @@ -49,6 +59,8 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { @After public void after() { + executor.shutdownNow(); + tenantService.deleteTenant(tenantId); } @@ -130,22 +142,24 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { } @Test - public void testFindCustomersByTenantId() { + public void testFindCustomersByTenantId() throws Exception { Tenant tenant = new Tenant(); tenant.setTitle("Test tenant"); tenant = tenantService.saveTenant(tenant); TenantId tenantId = tenant.getId(); - List customers = new ArrayList<>(); + List> futures = new ArrayList<>(135); for (int i = 0; i < 135; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); customer.setTitle("Customer" + i); - customers.add(customerService.saveCustomer(customer)); + futures.add(executor.submit(() -> + customerService.saveCustomer(customer))); } + List customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - List loadedCustomers = new ArrayList<>(); + List loadedCustomers = new ArrayList<>(135); PageLink pageLink = new PageLink(23); PageData pageData = null; do { @@ -156,10 +170,7 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { } } while (pageData.hasNext()); - Collections.sort(customers, idComparator); - Collections.sort(loadedCustomers, idComparator); - - Assert.assertEquals(customers, loadedCustomers); + assertThat(customers).containsExactlyInAnyOrderElementsOf(loadedCustomers); customerService.deleteCustomersByTenantId(tenantId); @@ -172,31 +183,34 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { } @Test - public void testFindCustomersByTenantIdAndTitle() { + public void testFindCustomersByTenantIdAndTitle() throws Exception { String title1 = "Customer title 1"; - List customersTitle1 = new ArrayList<>(); + List> futures = new ArrayList<>(143); for (int i = 0; i < 143; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); + String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); - customersTitle1.add(customerService.saveCustomer(customer)); + futures.add(executor.submit(() -> customerService.saveCustomer(customer))); } + List customersTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + String title2 = "Customer title 2"; - List customersTitle2 = new ArrayList<>(); + futures = new ArrayList<>(175); for (int i = 0; i < 175; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); - String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); + String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10)); String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); customer.setTitle(title); - customersTitle2.add(customerService.saveCustomer(customer)); + futures.add(executor.submit(() -> customerService.saveCustomer(customer))); } + List customersTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - List loadedCustomersTitle1 = new ArrayList<>(); + List loadedCustomersTitle1 = new ArrayList<>(143); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; do { @@ -207,12 +221,9 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { } } while (pageData.hasNext()); - Collections.sort(customersTitle1, idComparator); - Collections.sort(loadedCustomersTitle1, idComparator); - - Assert.assertEquals(customersTitle1, loadedCustomersTitle1); + assertThat(customersTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle1); - List loadedCustomersTitle2 = new ArrayList<>(); + List loadedCustomersTitle2 = new ArrayList<>(175); pageLink = new PageLink(4, 0, title2); do { pageData = customerService.findCustomersByTenantId(tenantId, pageLink); @@ -222,23 +233,30 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest { } } while (pageData.hasNext()); - Collections.sort(customersTitle2, idComparator); - Collections.sort(loadedCustomersTitle2, idComparator); - - Assert.assertEquals(customersTitle2, loadedCustomersTitle2); + assertThat(customersTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle2); + List> deleteFutures = new ArrayList<>(143); for (Customer customer : loadedCustomersTitle1) { - customerService.deleteCustomer(tenantId, customer.getId()); + deleteFutures.add(executor.submit(() -> { + customerService.deleteCustomer(tenantId, customer.getId()); + return null; + })); } + Futures.allAsList(deleteFutures).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4, 0, title1); pageData = customerService.findCustomersByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); + deleteFutures = new ArrayList<>(175); for (Customer customer : loadedCustomersTitle2) { - customerService.deleteCustomer(tenantId, customer.getId()); + deleteFutures.add(executor.submit(() -> { + customerService.deleteCustomer(tenantId, customer.getId()); + return null; + })); } + Futures.allAsList(deleteFutures).get(TIMEOUT, TimeUnit.SECONDS); pageLink = new PageLink(4, 0, title2); pageData = customerService.findCustomersByTenantId(tenantId, pageLink); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java index 85951fad5a..770b215adf 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java @@ -418,7 +418,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest { edge.setType("default"); edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); try { dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId()); } finally { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java index 8d683abfb8..689752f0fe 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java @@ -49,6 +49,9 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest @Autowired private DeviceCredentialsService deviceCredentialsService; + @Autowired + private DataValidator credentialsValidator; + private DeviceCredentialsDao deviceCredentialsDao; private DeviceService deviceService; @@ -59,10 +62,15 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest @Before public void setup() throws Exception { - deviceCredentialsDao = mock(DeviceCredentialsDao.class); deviceService = mock(DeviceService.class); + deviceCredentialsDao = mock(DeviceCredentialsDao.class); + + ReflectionTestUtils.setField(credentialsValidator, "deviceService", deviceService); + ReflectionTestUtils.setField(credentialsValidator, "deviceCredentialsDao", deviceCredentialsDao); + ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "deviceCredentialsDao", deviceCredentialsDao); - ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "deviceService", deviceService); + ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "credentialsValidator", credentialsValidator); + } @After diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java index fae4aeea6a..4e0b0e7dfc 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java @@ -45,6 +45,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import static org.assertj.core.api.Assertions.assertThat; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; public abstract class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -252,6 +253,22 @@ public abstract class BaseDeviceProfileServiceTest extends AbstractServiceTest { deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); } + @Test + public void testDeleteDeviceProfileWithExistingOta_cascadeDelete() { + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); + deviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + OtaPackage otaPackage = constructDefaultOtaPackage(tenantId, deviceProfile.getId()); + otaPackage = otaPackageService.saveOtaPackage(otaPackage); + + assertThat(deviceProfileService.findDeviceProfileById(tenantId, deviceProfile.getId())).isNotNull(); + assertThat(otaPackageService.findOtaPackageById(tenantId, otaPackage.getId())).isNotNull(); + + deviceProfileService.deleteDeviceProfile(tenantId, deviceProfile.getId()); + + assertThat(deviceProfileService.findDeviceProfileById(tenantId, deviceProfile.getId())).isNull(); + assertThat(otaPackageService.findOtaPackageById(tenantId, otaPackage.getId())).isNull(); + } + @Test public void testDeleteDeviceProfile() { DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); 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 aa180a779d..33cb7e1bc1 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 @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -34,6 +36,8 @@ import java.io.IOException; import java.time.LocalDateTime; import java.time.Month; import java.time.ZoneOffset; +import java.util.ArrayList; +import java.util.List; public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { @@ -41,8 +45,14 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { public void saveEdgeEvent() throws Exception { EdgeId edgeId = new EdgeId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased()); - EdgeEvent edgeEvent = generateEdgeEvent(null, edgeId, deviceId, EdgeEventActionType.ADDED); - EdgeEvent saved = edgeEventService.save(edgeEvent); + TenantId tenantId = new TenantId(Uuids.timeBased()); + EdgeEvent edgeEvent = generateEdgeEvent(tenantId, edgeId, deviceId, EdgeEventActionType.ADDED); + edgeEventService.saveAsync(edgeEvent).get(); + + PageData edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, new TimePageLink(1), false); + Assert.assertFalse(edgeEvents.getData().isEmpty()); + + EdgeEvent saved = edgeEvents.getData().get(0); Assert.assertEquals(saved.getTenantId(), edgeEvent.getTenantId()); Assert.assertEquals(saved.getEdgeId(), edgeEvent.getEdgeId()); Assert.assertEquals(saved.getEntityId(), edgeEvent.getEntityId()); @@ -77,27 +87,31 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { EdgeId edgeId = new EdgeId(Uuids.timeBased()); DeviceId deviceId = new DeviceId(Uuids.timeBased()); TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); - saveEdgeEventWithProvidedTime(timeBeforeStartTime, edgeId, deviceId, tenantId); - EdgeEvent savedEdgeEvent = saveEdgeEventWithProvidedTime(eventTime, edgeId, deviceId, tenantId); - EdgeEvent savedEdgeEvent2 = saveEdgeEventWithProvidedTime(eventTime + 1, edgeId, deviceId, tenantId); - EdgeEvent savedEdgeEvent3 = saveEdgeEventWithProvidedTime(eventTime + 2, edgeId, deviceId, tenantId); - saveEdgeEventWithProvidedTime(timeAfterEndTime, edgeId, deviceId, tenantId); + + List> futures = new ArrayList<>(); + futures.add(saveEdgeEventWithProvidedTime(timeBeforeStartTime, edgeId, deviceId, tenantId)); + futures.add(saveEdgeEventWithProvidedTime(eventTime, edgeId, deviceId, tenantId)); + futures.add(saveEdgeEventWithProvidedTime(eventTime + 1, edgeId, deviceId, tenantId)); + futures.add(saveEdgeEventWithProvidedTime(eventTime + 2, edgeId, deviceId, tenantId)); + futures.add(saveEdgeEventWithProvidedTime(timeAfterEndTime, edgeId, deviceId, tenantId)); + + Futures.allAsList(futures).get(); TimePageLink pageLink = new TimePageLink(2, 0, "", new SortOrder("createdTime", SortOrder.Direction.DESC), startTime, endTime); PageData edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true); Assert.assertNotNull(edgeEvents.getData()); - Assert.assertTrue(edgeEvents.getData().size() == 2); - Assert.assertTrue(edgeEvents.getData().get(0).getUuidId().equals(savedEdgeEvent3.getUuidId())); - Assert.assertTrue(edgeEvents.getData().get(1).getUuidId().equals(savedEdgeEvent2.getUuidId())); + Assert.assertEquals(2, edgeEvents.getData().size()); + Assert.assertEquals(Uuids.startOf(eventTime + 2), edgeEvents.getData().get(0).getUuidId()); + Assert.assertEquals(Uuids.startOf(eventTime + 1), edgeEvents.getData().get(1).getUuidId()); Assert.assertTrue(edgeEvents.hasNext()); Assert.assertNotNull(pageLink.nextPageLink()); edgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink.nextPageLink(), true); Assert.assertNotNull(edgeEvents.getData()); - Assert.assertTrue(edgeEvents.getData().size() == 1); - Assert.assertTrue(edgeEvents.getData().get(0).getUuidId().equals(savedEdgeEvent.getUuidId())); + Assert.assertEquals(1, edgeEvents.getData().size()); + Assert.assertEquals(Uuids.startOf(eventTime), edgeEvents.getData().get(0).getUuidId()); Assert.assertFalse(edgeEvents.hasNext()); } @@ -109,7 +123,7 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { TimePageLink pageLink = new TimePageLink(1, 0, null, new SortOrder("createdTime", SortOrder.Direction.ASC)); EdgeEvent edgeEventWithTsUpdate = generateEdgeEvent(tenantId, edgeId, deviceId, EdgeEventActionType.TIMESERIES_UPDATED); - edgeEventService.save(edgeEventWithTsUpdate); + edgeEventService.saveAsync(edgeEventWithTsUpdate).get(); PageData allEdgeEvents = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, true); PageData edgeEventsWithoutTsUpdate = edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false); @@ -121,9 +135,9 @@ public abstract class BaseEdgeEventServiceTest extends AbstractServiceTest { Assert.assertTrue(edgeEventsWithoutTsUpdate.getData().isEmpty()); } - private EdgeEvent saveEdgeEventWithProvidedTime(long time, EdgeId edgeId, EntityId entityId, TenantId tenantId) throws Exception { + private ListenableFuture 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.save(edgeEvent); + return edgeEventService.saveAsync(edgeEvent); } } \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java index dcf8957b0e..0bbbf82452 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java @@ -60,7 +60,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { @Test public void testSaveEdge() { Edge edge = constructEdge("My edge", "default"); - Edge savedEdge = edgeService.saveEdge(edge, true); + Edge savedEdge = edgeService.saveEdge(edge); Assert.assertNotNull(savedEdge); Assert.assertNotNull(savedEdge.getId()); @@ -72,7 +72,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { savedEdge.setName("My new edge"); - edgeService.saveEdge(savedEdge, true); + edgeService.saveEdge(savedEdge); Edge foundEdge = edgeService.findEdgeById(tenantId, savedEdge.getId()); Assert.assertEquals(foundEdge.getName(), savedEdge.getName()); @@ -84,7 +84,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { Edge edge = new Edge(); edge.setType("default"); edge.setTenantId(tenantId); - edgeService.saveEdge(edge, true); + edgeService.saveEdge(edge); } @Test(expected = DataValidationException.class) @@ -92,7 +92,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { Edge edge = new Edge(); edge.setName("My edge"); edge.setType("default"); - edgeService.saveEdge(edge, true); + edgeService.saveEdge(edge); } @Test(expected = DataValidationException.class) @@ -101,13 +101,13 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { edge.setName("My edge"); edge.setType("default"); edge.setTenantId(TenantId.fromUUID(Uuids.timeBased())); - edgeService.saveEdge(edge, true); + edgeService.saveEdge(edge); } @Test(expected = DataValidationException.class) public void testAssignEdgeToNonExistentCustomer() { Edge edge = constructEdge("My edge", "default"); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); try { edgeService.assignEdgeToCustomer(tenantId, edge.getId(), new CustomerId(Uuids.timeBased())); } finally { @@ -118,7 +118,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testAssignEdgeToCustomerFromDifferentTenant() { Edge edge = constructEdge("My edge", "default"); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); Tenant tenant = new Tenant(); tenant.setTitle("Test different tenant"); tenant = tenantService.saveTenant(tenant); @@ -137,7 +137,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { @Test public void testFindEdgeById() { Edge edge = constructEdge("My edge", "default"); - Edge savedEdge = edgeService.saveEdge(edge, true); + Edge savedEdge = edgeService.saveEdge(edge); Edge foundEdge = edgeService.findEdgeById(tenantId, savedEdge.getId()); Assert.assertNotNull(foundEdge); Assert.assertEquals(savedEdge, foundEdge); @@ -150,15 +150,15 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { try { for (int i = 0; i < 3; i++) { Edge edge = constructEdge("My edge B" + i, "typeB"); - edges.add(edgeService.saveEdge(edge, true)); + edges.add(edgeService.saveEdge(edge)); } for (int i = 0; i < 7; i++) { Edge edge = constructEdge("My edge C" + i, "typeC"); - edges.add(edgeService.saveEdge(edge, true)); + edges.add(edgeService.saveEdge(edge)); } for (int i = 0; i < 9; i++) { Edge edge = constructEdge("My edge A" + i, "typeA"); - edges.add(edgeService.saveEdge(edge, true)); + edges.add(edgeService.saveEdge(edge)); } List edgeTypes = edgeService.findEdgeTypesByTenantId(tenantId).get(); Assert.assertNotNull(edgeTypes); @@ -176,7 +176,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { @Test public void testDeleteEdge() { Edge edge = constructEdge("My edge", "default"); - Edge savedEdge = edgeService.saveEdge(edge, true); + Edge savedEdge = edgeService.saveEdge(edge); Edge foundEdge = edgeService.findEdgeById(tenantId, savedEdge.getId()); Assert.assertNotNull(foundEdge); edgeService.deleteEdge(tenantId, savedEdge.getId()); @@ -195,7 +195,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { List edges = new ArrayList<>(); for (int i = 0; i < 178; i++) { Edge edge = constructEdge(tenantId, "Edge " + i, "default"); - edges.add(edgeService.saveEdge(edge, true)); + edges.add(edgeService.saveEdge(edge)); } List loadedEdges = new ArrayList<>(); @@ -233,7 +233,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); - edgesTitle1.add(edgeService.saveEdge(edge, true)); + edgesTitle1.add(edgeService.saveEdge(edge)); } String title2 = "Edge title 2"; List edgesTitle2 = new ArrayList<>(); @@ -242,7 +242,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); - edgesTitle2.add(edgeService.saveEdge(edge, true)); + edgesTitle2.add(edgeService.saveEdge(edge)); } List loadedEdgesTitle1 = new ArrayList<>(); @@ -305,7 +305,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type1); - edgesType1.add(edgeService.saveEdge(edge, true)); + edgesType1.add(edgeService.saveEdge(edge)); } String title2 = "Edge title 2"; String type2 = "typeB"; @@ -315,7 +315,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type2); - edgesType2.add(edgeService.saveEdge(edge, true)); + edgesType2.add(edgeService.saveEdge(edge)); } List loadedEdgesType1 = new ArrayList<>(); @@ -385,7 +385,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { List edges = new ArrayList<>(); for (int i = 0; i < 278; i++) { Edge edge = constructEdge(tenantId, "Edge" + i, "default"); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); edges.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId)); } @@ -431,7 +431,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); edgesTitle1.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId)); } String title2 = "Edge title 2"; @@ -441,7 +441,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, "default"); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); edgesTitle2.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId)); } @@ -513,7 +513,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title1 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type1); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); edgesType1.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId)); } String title2 = "Edge title 2"; @@ -524,7 +524,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String name = title2 + suffix; name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); Edge edge = constructEdge(name, type2); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); edgesType2.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId)); } @@ -589,12 +589,12 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { String edgeNameAfterRename = RandomStringUtils.randomAlphanumeric(15); Edge edge = constructEdge(tenantId, edgeNameBeforeRename, "default"); - edgeService.saveEdge(edge, true); + edgeService.saveEdge(edge); Edge savedEdge = edgeService.findEdgeByTenantIdAndName(tenantId, edgeNameBeforeRename); savedEdge.setName(edgeNameAfterRename); - edgeService.saveEdge(savedEdge, true); + edgeService.saveEdge(savedEdge); Edge renamedEdge = edgeService.findEdgeByTenantIdAndName(tenantId, edgeNameBeforeRename); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index 820b500b8c..430ac85391 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -21,8 +21,8 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; -import org.hamcrest.Matchers; import org.apache.commons.lang3.StringUtils; +import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -91,8 +91,8 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; @Slf4j public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -241,7 +241,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { List edges = new ArrayList<>(); for (int i = 0; i < 97; i++) { Edge edge = createEdge(i, "default"); - edges.add(edgeService.saveEdge(edge, true)); + edges.add(edgeService.saveEdge(edge)); } EdgeTypeFilter filter = new EdgeTypeFilter(); @@ -279,7 +279,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { public void testCountHierarchicalEntitiesByEdgeSearchQuery() throws InterruptedException { for (int i = 0; i < 5; i++) { Edge edge = createEdge(i, "type" + i); - edge = edgeService.saveEdge(edge, true); + edge = edgeService.saveEdge(edge); //TO make sure devices have different created time Thread.sleep(1); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java index d49594a955..7dedf14bab 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java @@ -488,7 +488,6 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } finally { deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); - otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java index 861b383e8e..92a3a6af3a 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java @@ -495,7 +495,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { @Test public void testFindEdgeRuleChainsByTenantIdAndName() { Edge edge = constructEdge(tenantId, "My edge", "default"); - Edge savedEdge = edgeService.saveEdge(edge, true); + Edge savedEdge = edgeService.saveEdge(edge); String name1 = "Edge RuleChain name 1"; List ruleChainsName1 = new ArrayList<>(); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java index 155db106ff..7d81e5afc6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service.event; import com.datastax.oss.driver.api.core.uuid.Uuids; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; @@ -35,6 +36,7 @@ import java.time.LocalDateTime; import java.time.Month; import java.time.ZoneOffset; import java.util.Optional; +import java.util.concurrent.ExecutionException; public abstract class BaseEventServiceTest extends AbstractServiceTest { @@ -42,21 +44,13 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { public void saveEvent() throws Exception { DeviceId devId = new DeviceId(Uuids.timeBased()); Event event = generateEvent(null, devId, "ALARM", Uuids.timeBased().toString()); - Event saved = eventService.save(event); + eventService.saveAsync(event).get(); Optional loaded = eventService.findEvent(event.getTenantId(), event.getEntityId(), event.getType(), event.getUid()); Assert.assertTrue(loaded.isPresent()); Assert.assertNotNull(loaded.get()); - Assert.assertEquals(saved, loaded.get()); - } - - @Test - public void saveEventIfNotExists() throws Exception { - DeviceId devId = new DeviceId(Uuids.timeBased()); - Event event = generateEvent(null, devId, "ALARM", Uuids.timeBased().toString()); - Optional saved = eventService.saveIfNotExists(event); - Assert.assertTrue(saved.isPresent()); - saved = eventService.saveIfNotExists(event); - Assert.assertFalse(saved.isPresent()); + Assert.assertEquals(event.getEntityId(), loaded.get().getEntityId()); + Assert.assertEquals(event.getType(), loaded.get().getType()); + Assert.assertEquals(event.getBody(), loaded.get().getBody()); } @Test @@ -96,11 +90,11 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { @Test public void findEventsByTypeAndTimeDescOrder() throws Exception { - long timeBeforeStartTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC); - long startTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC); - long eventTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC); - long endTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC); - long timeAfterEndTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC); + long timeBeforeStartTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC); + long startTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC); + long eventTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC); + long endTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC); + long timeAfterEndTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC); CustomerId customerId = new CustomerId(Uuids.timeBased()); TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); @@ -129,9 +123,10 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest { Assert.assertFalse(events.hasNext()); } - private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws IOException { + private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws Exception { Event event = generateEvent(tenantId, entityId, DataConstants.STATS, null); event.setId(new EventId(Uuids.startOf(time))); - return eventService.save(event); + eventService.saveAsync(event).get(); + return event; } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/install/sql/EntitiesSchemaSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/install/sql/EntitiesSchemaSqlTest.java new file mode 100644 index 0000000000..285ea5bb50 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/install/sql/EntitiesSchemaSqlTest.java @@ -0,0 +1,72 @@ +/** + * Copyright © 2016-2022 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.dao.service.install.sql; + +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.jdbc.core.JdbcTemplate; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +@DaoSqlTest +public class EntitiesSchemaSqlTest extends AbstractServiceTest { + + @Value("${classpath:sql/schema-entities.sql}") + private Path installScriptPath; + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Test + public void testRepeatedInstall() throws IOException { + String installScript = Files.readString(installScriptPath); + try { + for (int i = 1; i <= 2; i++) { + jdbcTemplate.execute(installScript); + } + } catch (Exception e) { + Assertions.fail("Failed to execute reinstall", e); + } + } + + @Test + public void testRepeatedInstall_badScript() { + String illegalInstallScript = "CREATE TABLE IF NOT EXISTS qwerty ();\n" + + "ALTER TABLE qwerty ADD COLUMN first VARCHAR(10);"; + + assertDoesNotThrow(() -> { + jdbcTemplate.execute(illegalInstallScript); + }); + + try { + assertThatThrownBy(() -> { + jdbcTemplate.execute(illegalInstallScript); + }).getCause().hasMessageContaining("column").hasMessageContaining("already exists"); + } finally { + jdbcTemplate.execute("DROP TABLE qwerty;"); + } + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java index adffcc1bd6..aa99a88ac7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.sql.alarm; import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.alarm.Alarm; @@ -28,6 +29,8 @@ import org.thingsboard.server.dao.alarm.AlarmDao; import java.util.UUID; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -35,6 +38,7 @@ import static org.junit.Assert.assertNotNull; /** * Created by Valerii Sosliuk on 5/21/2017. */ +@Slf4j public class JpaAlarmDaoTest extends AbstractJpaDaoTest { @Autowired @@ -42,21 +46,23 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest { @Test - public void testFindLatestByOriginatorAndType() throws ExecutionException, InterruptedException { - System.out.println(System.currentTimeMillis()); + public void testFindLatestByOriginatorAndType() throws ExecutionException, InterruptedException, TimeoutException { + log.info("Current system time in millis = {}", System.currentTimeMillis()); UUID tenantId = UUID.fromString("d4b68f40-3e96-11e7-a884-898080180d6b"); UUID originator1Id = UUID.fromString("d4b68f41-3e96-11e7-a884-898080180d6b"); UUID originator2Id = UUID.fromString("d4b68f42-3e96-11e7-a884-898080180d6b"); UUID alarm1Id = UUID.fromString("d4b68f43-3e96-11e7-a884-898080180d6b"); UUID alarm2Id = UUID.fromString("d4b68f44-3e96-11e7-a884-898080180d6b"); UUID alarm3Id = UUID.fromString("d4b68f45-3e96-11e7-a884-898080180d6b"); + int alarmCountBeforeSave = alarmDao.find(TenantId.fromUUID(tenantId)).size(); saveAlarm(alarm1Id, tenantId, originator1Id, "TEST_ALARM"); saveAlarm(alarm2Id, tenantId, originator1Id, "TEST_ALARM"); saveAlarm(alarm3Id, tenantId, originator2Id, "TEST_ALARM"); - assertEquals(3, alarmDao.find(TenantId.fromUUID(tenantId)).size()); + int alarmCountAfterSave = alarmDao.find(TenantId.fromUUID(tenantId)).size(); + assertEquals(3, alarmCountAfterSave - alarmCountBeforeSave); ListenableFuture future = alarmDao .findLatestByOriginatorAndType(TenantId.fromUUID(tenantId), new DeviceId(originator1Id), "TEST_ALARM"); - Alarm alarm = future.get(); + Alarm alarm = future.get(30, TimeUnit.SECONDS); assertNotNull(alarm); assertEquals(alarm2Id, alarm.getId().getId()); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java index fda1b21335..727f329229 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/asset/JpaAssetDaoTest.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.sql.asset; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.ListenableFuture; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.EntitySubtype; @@ -34,9 +36,11 @@ import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; -import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -45,23 +49,39 @@ import static org.junit.Assert.assertTrue; */ public class JpaAssetDaoTest extends AbstractJpaDaoTest { + UUID tenantId1; + UUID tenantId2; + UUID customerId1; + UUID customerId2; + List assets = new ArrayList<>(); @Autowired private AssetDao assetDao; - @Test - public void testFindAssetsByTenantId() { - UUID tenantId1 = Uuids.timeBased(); - UUID tenantId2 = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); + @Before + public void setUp() { + tenantId1 = Uuids.timeBased(); + tenantId2 = Uuids.timeBased(); + customerId1 = Uuids.timeBased(); + customerId2 = Uuids.timeBased(); for (int i = 0; i < 60; i++) { UUID assetId = Uuids.timeBased(); UUID tenantId = i % 2 == 0 ? tenantId1 : tenantId2; UUID customerId = i % 2 == 0 ? customerId1 : customerId2; - saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1"); + assets.add(saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1")); } - assertEquals(60, assetDao.find(TenantId.fromUUID(tenantId1)).size()); + assertEquals(assets.size(), assetDao.find(TenantId.fromUUID(tenantId1)).size()); + } + @After + public void tearDown() { + for (Asset asset : assets) { + assetDao.removeById(asset.getTenantId(), asset.getUuidId()); + } + assets.clear(); + } + + @Test + public void testFindAssetsByTenantId() { PageLink pageLink = new PageLink(20, 0, "ASSET_"); PageData assets1 = assetDao.findAssetsByTenantId(tenantId1, pageLink); assertEquals(20, assets1.getData().size()); @@ -77,18 +97,7 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest { @Test public void testFindAssetsByTenantIdAndCustomerId() { - UUID tenantId1 = Uuids.timeBased(); - UUID tenantId2 = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); - for (int i = 0; i < 60; i++) { - UUID assetId = Uuids.timeBased(); - UUID tenantId = i % 2 == 0 ? tenantId1 : tenantId2; - UUID customerId = i % 2 == 0 ? customerId1 : customerId2; - saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1"); - } - - PageLink pageLink = new PageLink(20, 0, "ASSET_"); + PageLink pageLink = new PageLink(20, 0, "ASSET_"); PageData assets1 = assetDao.findAssetsByTenantIdAndCustomerId(tenantId1, customerId1, pageLink); assertEquals(20, assets1.getData().size()); @@ -102,62 +111,46 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest { } @Test - public void testFindAssetsByTenantIdAndIdsAsync() throws ExecutionException, InterruptedException { - UUID tenantId = Uuids.timeBased(); - UUID customerId = Uuids.timeBased(); - List searchIds = new ArrayList<>(); - for (int i = 0; i < 30; i++) { - UUID assetId = Uuids.timeBased(); - saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1"); - if (i % 3 == 0) { - searchIds.add(assetId); - } - } + public void testFindAssetsByTenantIdAndIdsAsync() throws ExecutionException, InterruptedException, TimeoutException { + List searchIds = getAssetsUuids(tenantId1); ListenableFuture> assetsFuture = assetDao - .findAssetsByTenantIdAndIdsAsync(tenantId, searchIds); - List assets = assetsFuture.get(); + .findAssetsByTenantIdAndIdsAsync(tenantId1, searchIds); + List assets = assetsFuture.get(30, TimeUnit.SECONDS); assertNotNull(assets); - assertEquals(10, assets.size()); + assertEquals(searchIds.size(), assets.size()); } @Test - public void testFindAssetsByTenantIdCustomerIdAndIdsAsync() throws ExecutionException, InterruptedException { - UUID tenantId = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); - List searchIds = new ArrayList<>(); - for (int i = 0; i < 30; i++) { - UUID assetId = Uuids.timeBased(); - UUID customerId = i%2 == 0 ? customerId1 : customerId2; - saveAsset(assetId, tenantId, customerId, "ASSET_" + i, "TYPE_1"); - if (i % 3 == 0) { - searchIds.add(assetId); - } - } + public void testFindAssetsByTenantIdCustomerIdAndIdsAsync() throws ExecutionException, InterruptedException, TimeoutException { + List searchIds = getAssetsUuids(tenantId1); ListenableFuture> assetsFuture = assetDao - .findAssetsByTenantIdAndCustomerIdAndIdsAsync(tenantId, customerId1, searchIds); - List assets = assetsFuture.get(); + .findAssetsByTenantIdAndCustomerIdAndIdsAsync(tenantId1, customerId1, searchIds); + List assets = assetsFuture.get(30, TimeUnit.SECONDS); assertNotNull(assets); - assertEquals(5, assets.size()); + assertEquals(searchIds.size(), assets.size()); + } + + private List getAssetsUuids(UUID tenantId) { + List result = new ArrayList<>(); + for (Asset asset : assets) { + if (asset.getTenantId().getId().equals(tenantId)) { + result.add(asset.getUuidId()); + } + } + return result; } @Test public void testFindAssetsByTenantIdAndName() { - UUID assetId1 = Uuids.timeBased(); - UUID assetId2 = Uuids.timeBased(); - UUID tenantId1 = Uuids.timeBased(); - UUID tenantId2 = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); + UUID assetId = Uuids.timeBased(); String name = "TEST_ASSET"; - saveAsset(assetId1, tenantId1, customerId1, name, "TYPE_1"); - saveAsset(assetId2, tenantId2, customerId2, name, "TYPE_1"); + assets.add(saveAsset(assetId, tenantId2, customerId2, name, "TYPE_1")); Optional assetOpt1 = assetDao.findAssetsByTenantIdAndName(tenantId2, name); assertTrue("Optional expected to be non-empty", assetOpt1.isPresent()); - assertEquals(assetId2, assetOpt1.get().getId().getId()); + assertEquals(assetId, assetOpt1.get().getId().getId()); Optional assetOpt2 = assetDao.findAssetsByTenantIdAndName(tenantId2, "NON_EXISTENT_NAME"); assertFalse("Optional expected to be empty", assetOpt2.isPresent()); @@ -165,58 +158,61 @@ public class JpaAssetDaoTest extends AbstractJpaDaoTest { @Test public void testFindAssetsByTenantIdAndType() { - // TODO: implement + String type = "TYPE_2"; + assets.add(saveAsset(Uuids.timeBased(), tenantId2, customerId2, "TEST_ASSET", type)); + + List foundedAssetsByType = assetDao + .findAssetsByTenantIdAndType(tenantId2, type, new PageLink(3)).getData(); + compareFoundedAssetByType(foundedAssetsByType, type); } @Test public void testFindAssetsByTenantIdAndCustomerIdAndType() { - // TODO: implement + String type = "TYPE_2"; + assets.add(saveAsset(Uuids.timeBased(), tenantId2, customerId2, "TEST_ASSET", type)); + + List foundedAssetsByType = assetDao + .findAssetsByTenantIdAndCustomerIdAndType(tenantId2, customerId2, type, new PageLink(3)).getData(); + compareFoundedAssetByType(foundedAssetsByType, type); + } + + private void compareFoundedAssetByType(List foundedAssetsByType, String type) { + assertNotNull(foundedAssetsByType); + assertEquals(1, foundedAssetsByType.size()); + assertEquals(type, foundedAssetsByType.get(0).getType()); } @Test - public void testFindTenantAssetTypesAsync() throws ExecutionException, InterruptedException { - UUID assetId1 = Uuids.timeBased(); - UUID assetId2 = Uuids.timeBased(); - UUID tenantId1 = Uuids.timeBased(); - UUID tenantId2 = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); - saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_1", "TYPE_1"); - saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_2", "TYPE_1"); - saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_3", "TYPE_2"); - saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_4", "TYPE_3"); - saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_5", "TYPE_3"); - saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_6", "TYPE_3"); - - saveAsset(Uuids.timeBased(), tenantId2, customerId2, "TEST_ASSET_7", "TYPE_4"); - saveAsset(Uuids.timeBased(), tenantId2, customerId2, "TEST_ASSET_8", "TYPE_1"); - saveAsset(Uuids.timeBased(), tenantId2, customerId2, "TEST_ASSET_9", "TYPE_1"); - - List tenant1Types = assetDao.findTenantAssetTypesAsync(tenantId1).get(); + public void testFindTenantAssetTypesAsync() throws ExecutionException, InterruptedException, TimeoutException { + // Assets with type "TYPE_1" added in setUp method + assets.add(saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_3", "TYPE_2")); + assets.add(saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_4", "TYPE_3")); + assets.add(saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_5", "TYPE_3")); + assets.add(saveAsset(Uuids.timeBased(), tenantId1, customerId1, "TEST_ASSET_6", "TYPE_3")); + + assets.add(saveAsset(Uuids.timeBased(), tenantId2, customerId2, "TEST_ASSET_7", "TYPE_4")); + + List tenant1Types = assetDao.findTenantAssetTypesAsync(tenantId1).get(30, TimeUnit.SECONDS); assertNotNull(tenant1Types); - List tenant2Types = assetDao.findTenantAssetTypesAsync(tenantId2).get(); + List tenant2Types = assetDao.findTenantAssetTypesAsync(tenantId2).get(30, TimeUnit.SECONDS); assertNotNull(tenant2Types); - assertEquals(3, tenant1Types.size()); - assertTrue(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_1"))); - assertTrue(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_2"))); - assertTrue(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_3"))); - assertFalse(tenant1Types.stream().anyMatch(t -> t.getType().equals("TYPE_4"))); - - assertEquals(2, tenant2Types.size()); - assertTrue(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_1"))); - assertTrue(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_4"))); - assertFalse(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_2"))); - assertFalse(tenant2Types.stream().anyMatch(t -> t.getType().equals("TYPE_3"))); + List types = List.of("TYPE_1", "TYPE_2", "TYPE_3", "TYPE_4"); + assertEquals(getDifferentTypesCount(types, tenant1Types), tenant1Types.size()); + assertEquals(getDifferentTypesCount(types, tenant2Types), tenant2Types.size()); + } + + private long getDifferentTypesCount(List types, List foundedAssetsTypes) { + return foundedAssetsTypes.stream().filter(type -> types.contains(type.getType())).count(); } - private void saveAsset(UUID id, UUID tenantId, UUID customerId, String name, String type) { + private Asset saveAsset(UUID id, UUID tenantId, UUID customerId, String name, String type) { Asset asset = new Asset(); asset.setId(new AssetId(id)); asset.setTenantId(TenantId.fromUUID(tenantId)); asset.setCustomerId(new CustomerId(customerId)); asset.setName(name); asset.setType(type); - assetDao.save(TenantId.fromUUID(tenantId), asset); + return assetDao.save(TenantId.fromUUID(tenantId), asset); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java index c168a1e3db..510000b86e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/audit/JpaAuditLogDaoTest.java @@ -15,7 +15,144 @@ */ package org.thingsboard.server.dao.sql.audit; +import com.datastax.oss.driver.api.core.uuid.Uuids; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; +import org.thingsboard.server.dao.audit.AuditLogDao; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; public class JpaAuditLogDaoTest extends AbstractJpaDaoTest { + List auditLogList = new ArrayList<>(); + UUID tenantId; + CustomerId customerId1; + CustomerId customerId2; + UserId userId1; + UserId userId2; + EntityId entityId1; + EntityId entityId2; + AuditLog neededFoundedAuditLog; + @Autowired + private AuditLogDao auditLogDao; + + @Before + public void setUp() { + setUpIds(); + for (int i = 0; i < 60; i++) { + ActionType actionType = i % 2 == 0 ? ActionType.ADDED : ActionType.DELETED; + CustomerId customerId = i % 4 == 0 ? customerId1 : customerId2; + UserId userId = i % 6 == 0 ? userId1 : userId2; + EntityId entityId = i % 10 == 0 ? entityId1 : entityId2; + auditLogList.add(createAuditLog(i, actionType, customerId, userId, entityId)); + } + assertEquals(auditLogList.size(), auditLogDao.find(TenantId.fromUUID(tenantId)).size()); + neededFoundedAuditLog = auditLogList.get(0); + assertNotNull(neededFoundedAuditLog); + } + + private void setUpIds() { + tenantId = Uuids.timeBased(); + customerId1 = new CustomerId(Uuids.timeBased()); + customerId2 = new CustomerId(Uuids.timeBased()); + userId1 = new UserId(Uuids.timeBased()); + userId2 = new UserId(Uuids.timeBased()); + entityId1 = new DeviceId(Uuids.timeBased()); + entityId2 = new DeviceId(Uuids.timeBased()); + } + + @After + public void tearDown() { + for (AuditLog auditLog : auditLogList) { + auditLogDao.removeById(TenantId.fromUUID(tenantId), auditLog.getUuidId()); + } + auditLogList.clear(); + } + + @Test + public void testFindById() { + AuditLog foundedAuditLogById = auditLogDao.findById(TenantId.fromUUID(tenantId), neededFoundedAuditLog.getUuidId()); + checkFoundedAuditLog(foundedAuditLogById); + } + + @Test + public void testFindByIdAsync() throws ExecutionException, InterruptedException, TimeoutException { + AuditLog foundedAuditLogById = auditLogDao + .findByIdAsync(TenantId.fromUUID(tenantId), neededFoundedAuditLog.getUuidId()).get(30, TimeUnit.SECONDS); + checkFoundedAuditLog(foundedAuditLogById); + } + + private void checkFoundedAuditLog(AuditLog foundedAuditLogById) { + assertNotNull(foundedAuditLogById); + assertEquals(neededFoundedAuditLog, foundedAuditLogById); + } + + @Test + public void testFindAuditLogsByTenantId() { + List foundedAuditLogs = auditLogDao.findAuditLogsByTenantId(tenantId, + List.of(ActionType.ADDED), + new TimePageLink(40)).getData(); + checkFoundedAuditLogsList(foundedAuditLogs, 30); + } + + @Test + public void testFindAuditLogsByTenantIdAndCustomerId() { + List foundedAuditLogs = auditLogDao.findAuditLogsByTenantIdAndCustomerId(tenantId, + customerId1, + List.of(ActionType.ADDED), + new TimePageLink(20)).getData(); + checkFoundedAuditLogsList(foundedAuditLogs, 15); + } + + @Test + public void testFindAuditLogsByTenantIdAndUserId() { + List foundedAuditLogs = auditLogDao.findAuditLogsByTenantIdAndUserId(tenantId, + userId1, + List.of(ActionType.ADDED), + new TimePageLink(20)).getData(); + checkFoundedAuditLogsList(foundedAuditLogs, 10); + } + + @Test + public void testFindAuditLogsByTenantIdAndEntityId() { + List foundedAuditLogs = auditLogDao.findAuditLogsByTenantIdAndEntityId(tenantId, + entityId1, + List.of(ActionType.ADDED), + new TimePageLink(10)).getData(); + checkFoundedAuditLogsList(foundedAuditLogs, 6); + } + + private void checkFoundedAuditLogsList(List foundedAuditLogs, int neededSizeForFoundedList) { + assertNotNull(foundedAuditLogs); + assertEquals(neededSizeForFoundedList, foundedAuditLogs.size()); + } + + private AuditLog createAuditLog(int number, ActionType actionType, CustomerId customerId, UserId userId, EntityId entityId) { + AuditLog auditLog = new AuditLog(); + auditLog.setTenantId(TenantId.fromUUID(tenantId)); + auditLog.setCustomerId(customerId); + auditLog.setUserId(userId); + auditLog.setEntityId(entityId); + auditLog.setUserName("AUDIT_LOG_" + number); + auditLog.setActionType(actionType); + return auditLogDao.save(TenantId.fromUUID(tenantId), auditLog); + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java index 8e7225ec7f..85b3c89622 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDaoTest.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.sql.component; import com.datastax.oss.driver.api.core.uuid.Uuids; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.id.ComponentDescriptorId; @@ -37,34 +39,43 @@ import static org.junit.Assert.assertEquals; */ public class JpaBaseComponentDescriptorDaoTest extends AbstractJpaDaoTest { + final List componentTypes = List.of(ComponentType.FILTER, ComponentType.ACTION); @Autowired private ComponentDescriptorDao componentDescriptorDao; - @Test - public void findByType() { + @Before + public void setUp() { for (int i = 0; i < 20; i++) { createComponentDescriptor(ComponentType.FILTER, ComponentScope.SYSTEM, i); createComponentDescriptor(ComponentType.ACTION, ComponentScope.TENANT, i + 20); } + } + @After + public void tearDown() { + for (ComponentType componentType : componentTypes) { + List byTypeAndPageLink = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, + componentType, new PageLink(20)).getData(); + for (ComponentDescriptor descriptor : byTypeAndPageLink) { + componentDescriptorDao.deleteById(AbstractServiceTest.SYSTEM_TENANT_ID, descriptor.getId()); + } + } + } + + @Test + public void findByType() { PageLink pageLink = new PageLink(15, 0, "COMPONENT_"); PageData components1 = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, ComponentType.FILTER, pageLink); assertEquals(15, components1.getData().size()); pageLink = pageLink.nextPageLink(); - PageData components2 = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID,ComponentType.FILTER, pageLink); + PageData components2 = componentDescriptorDao.findByTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, ComponentType.FILTER, pageLink); assertEquals(5, components2.getData().size()); } @Test - public void findByTypeAndSocpe() { - for (int i = 0; i < 20; i++) { - createComponentDescriptor(ComponentType.ENRICHMENT, ComponentScope.SYSTEM, i); - createComponentDescriptor(ComponentType.ACTION, ComponentScope.TENANT, i + 20); - createComponentDescriptor(ComponentType.FILTER, ComponentScope.SYSTEM, i + 40); - } - - PageLink pageLink = new PageLink(15, 0, "COMPONENT_"); + public void findByTypeAndScope() { + PageLink pageLink = new PageLink(15, 0, "COMPONENT_"); PageData components1 = componentDescriptorDao.findByScopeAndTypeAndPageLink(AbstractServiceTest.SYSTEM_TENANT_ID, ComponentScope.SYSTEM, ComponentType.FILTER, pageLink); assertEquals(15, components1.getData().size()); @@ -81,7 +92,7 @@ public class JpaBaseComponentDescriptorDaoTest extends AbstractJpaDaoTest { component.setType(type); component.setScope(scope); component.setName("COMPONENT_" + index); - componentDescriptorDao.save(AbstractServiceTest.SYSTEM_TENANT_ID,component); + componentDescriptorDao.save(AbstractServiceTest.SYSTEM_TENANT_ID, component); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDaoTest.java index 206f734d6b..776a7d6135 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceCredentialsDaoTest.java @@ -15,14 +15,18 @@ */ package org.thingsboard.server.dao.sql.device; -import com.github.springtestdbunit.annotation.DatabaseSetup; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.device.DeviceCredentialsDao; -import org.thingsboard.server.dao.service.AbstractServiceTest; +import java.util.List; import java.util.UUID; import static org.junit.Assert.assertEquals; @@ -37,22 +41,44 @@ public class JpaDeviceCredentialsDaoTest extends AbstractJpaDaoTest { @Autowired DeviceCredentialsDao deviceCredentialsDao; + List deviceCredentialsList; + DeviceCredentials neededDeviceCredentials; + + @Before + public void setUp() { + deviceCredentialsList = List.of(createAndSaveDeviceCredentials(), createAndSaveDeviceCredentials()); + neededDeviceCredentials = deviceCredentialsList.get(0); + assertNotNull(neededDeviceCredentials); + } + + DeviceCredentials createAndSaveDeviceCredentials() { + DeviceCredentials deviceCredentials = new DeviceCredentials(); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + deviceCredentials.setCredentialsId(UUID.randomUUID().toString()); + deviceCredentials.setCredentialsValue("CHECK123"); + deviceCredentials.setDeviceId(new DeviceId(UUID.randomUUID())); + return deviceCredentialsDao.save(TenantId.SYS_TENANT_ID, deviceCredentials); + } + + @After + public void deleteDeviceCredentials() { + for (DeviceCredentials credentials : deviceCredentialsList) { + deviceCredentialsDao.removeById(TenantId.SYS_TENANT_ID, credentials.getUuidId()); + } + } + @Test - @DatabaseSetup("classpath:dbunit/device_credentials.xml") public void testFindByDeviceId() { - UUID deviceId = UUID.fromString("958e3a30-3215-11e7-93ae-92361f002671"); - DeviceCredentials deviceCredentials = deviceCredentialsDao.findByDeviceId(SYSTEM_TENANT_ID, deviceId); - assertNotNull(deviceCredentials); - assertEquals("958e3314-3215-11e7-93ae-92361f002671", deviceCredentials.getId().getId().toString()); - assertEquals("ID_1", deviceCredentials.getCredentialsId()); + DeviceCredentials foundedDeviceCredentials = deviceCredentialsDao.findByDeviceId(SYSTEM_TENANT_ID, neededDeviceCredentials.getDeviceId().getId()); + assertNotNull(foundedDeviceCredentials); + assertEquals(neededDeviceCredentials.getId(), foundedDeviceCredentials.getId()); + assertEquals(neededDeviceCredentials.getCredentialsId(), foundedDeviceCredentials.getCredentialsId()); } @Test - @DatabaseSetup("classpath:dbunit/device_credentials.xml") public void findByCredentialsId() { - String credentialsId = "ID_2"; - DeviceCredentials deviceCredentials = deviceCredentialsDao.findByCredentialsId(SYSTEM_TENANT_ID, credentialsId); - assertNotNull(deviceCredentials); - assertEquals("958e3c74-3215-11e7-93ae-92361f002671", deviceCredentials.getId().getId().toString()); + DeviceCredentials foundedDeviceCredentials = deviceCredentialsDao.findByCredentialsId(SYSTEM_TENANT_ID, neededDeviceCredentials.getCredentialsId()); + assertNotNull(foundedDeviceCredentials); + assertEquals(neededDeviceCredentials.getId(), foundedDeviceCredentials.getId()); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java index 7ce62c26c9..1c21e5bc33 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/device/JpaDeviceDaoTest.java @@ -20,10 +20,14 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; 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.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,12 +35,15 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.device.DeviceDao; +import org.thingsboard.server.dao.device.DeviceProfileDao; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -46,13 +53,49 @@ import static org.junit.Assert.assertNotNull; */ public class JpaDeviceDaoTest extends AbstractJpaDaoTest { + public static final int COUNT_DEVICES = 40; + public static final String PREFIX_FOR_DEVICE_NAME = "SEARCH_TEXT_"; + List deviceIds; + UUID tenantId1; + UUID tenantId2; + UUID customerId1; + UUID customerId2; @Autowired private DeviceDao deviceDao; + @Autowired + private DeviceProfileDao deviceProfileDao; + + private DeviceProfile savedDeviceProfile; + ListeningExecutorService executor; + @Before + public void setUp() { + createDeviceProfile(); + + tenantId1 = Uuids.timeBased(); + customerId1 = Uuids.timeBased(); + tenantId2 = Uuids.timeBased(); + customerId2 = Uuids.timeBased(); + + deviceIds = createDevices(tenantId1, tenantId2, customerId1, customerId2, COUNT_DEVICES); + } + + private void createDeviceProfile() { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setName("TEST"); + deviceProfile.setTenantId(TenantId.SYS_TENANT_ID); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTransportType(DeviceTransportType.DEFAULT); + deviceProfile.setDescription("Test"); + savedDeviceProfile = deviceProfileDao.save(TenantId.SYS_TENANT_ID, deviceProfile); + } + @After public void tearDown() throws Exception { + deviceDao.removeAllByIds(deviceIds); + deviceProfileDao.removeById(TenantId.SYS_TENANT_ID, savedDeviceProfile.getUuidId()); if (executor != null) { executor.shutdownNow(); } @@ -60,13 +103,7 @@ public class JpaDeviceDaoTest extends AbstractJpaDaoTest { @Test public void testFindDevicesByTenantId() { - UUID tenantId1 = Uuids.timeBased(); - UUID tenantId2 = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); - createDevices(tenantId1, tenantId2, customerId1, customerId2, 40); - - PageLink pageLink = new PageLink(15, 0, "SEARCH_TEXT"); + PageLink pageLink = new PageLink(15, 0, PREFIX_FOR_DEVICE_NAME); PageData devices1 = deviceDao.findDevicesByTenantId(tenantId1, pageLink); assertEquals(15, devices1.getData().size()); @@ -77,11 +114,12 @@ public class JpaDeviceDaoTest extends AbstractJpaDaoTest { } @Test - public void testFindAsync() throws ExecutionException, InterruptedException { + public void testFindAsync() throws ExecutionException, InterruptedException, TimeoutException { UUID tenantId = Uuids.timeBased(); UUID customerId = Uuids.timeBased(); - Device device = getDevice(tenantId, customerId); - deviceDao.save(TenantId.fromUUID(tenantId), device); + // send to method getDevice() number = 40, because make random name is bad and name "SEARCH_TEXT_40" don't used + Device device = getDevice(tenantId, customerId, 40); + deviceIds.add(deviceDao.save(TenantId.fromUUID(tenantId), device).getUuidId()); UUID uuid = device.getId().getId(); Device entity = deviceDao.findById(TenantId.fromUUID(tenantId), uuid); @@ -90,73 +128,44 @@ public class JpaDeviceDaoTest extends AbstractJpaDaoTest { executor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10, ThingsBoardThreadFactory.forName(getClass().getSimpleName() + "-test-scope"))); ListenableFuture future = executor.submit(() -> deviceDao.findById(TenantId.fromUUID(tenantId), uuid)); - Device asyncDevice = future.get(); + Device asyncDevice = future.get(30, TimeUnit.SECONDS); assertNotNull("Async device expected to be not null", asyncDevice); } @Test - public void testFindDevicesByTenantIdAndIdsAsync() throws ExecutionException, InterruptedException { - UUID tenantId1 = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID tenantId2 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); - - List deviceIds = new ArrayList<>(); - - for(int i = 0; i < 5; i++) { - UUID deviceId1 = Uuids.timeBased(); - UUID deviceId2 = Uuids.timeBased(); - deviceDao.save(TenantId.fromUUID(tenantId1), getDevice(tenantId1, customerId1, deviceId1)); - deviceDao.save(TenantId.fromUUID(tenantId2), getDevice(tenantId2, customerId2, deviceId2)); - deviceIds.add(deviceId1); - deviceIds.add(deviceId2); - } - + public void testFindDevicesByTenantIdAndIdsAsync() throws ExecutionException, InterruptedException, TimeoutException { ListenableFuture> devicesFuture = deviceDao.findDevicesByTenantIdAndIdsAsync(tenantId1, deviceIds); - List devices = devicesFuture.get(); - assertEquals(5, devices.size()); + List devices = devicesFuture.get(30, TimeUnit.SECONDS); + assertEquals(20, devices.size()); } @Test - public void testFindDevicesByTenantIdAndCustomerIdAndIdsAsync() throws ExecutionException, InterruptedException { - UUID tenantId1 = Uuids.timeBased(); - UUID customerId1 = Uuids.timeBased(); - UUID tenantId2 = Uuids.timeBased(); - UUID customerId2 = Uuids.timeBased(); - - List deviceIds = new ArrayList<>(); - - for(int i = 0; i < 20; i++) { - UUID deviceId1 = Uuids.timeBased(); - UUID deviceId2 = Uuids.timeBased(); - deviceDao.save(TenantId.fromUUID(tenantId1), getDevice(tenantId1, customerId1, deviceId1)); - deviceDao.save(TenantId.fromUUID(tenantId2), getDevice(tenantId2, customerId2, deviceId2)); - deviceIds.add(deviceId1); - deviceIds.add(deviceId2); - } - + public void testFindDevicesByTenantIdAndCustomerIdAndIdsAsync() throws ExecutionException, InterruptedException, TimeoutException { ListenableFuture> devicesFuture = deviceDao.findDevicesByTenantIdCustomerIdAndIdsAsync(tenantId1, customerId1, deviceIds); - List devices = devicesFuture.get(); + List devices = devicesFuture.get(30, TimeUnit.SECONDS); assertEquals(20, devices.size()); } - private void createDevices(UUID tenantId1, UUID tenantId2, UUID customerId1, UUID customerId2, int count) { + private List createDevices(UUID tenantId1, UUID tenantId2, UUID customerId1, UUID customerId2, int count) { + List savedDevicesUUID = new ArrayList<>(); for (int i = 0; i < count / 2; i++) { - deviceDao.save(TenantId.fromUUID(tenantId1), getDevice(tenantId1, customerId1)); - deviceDao.save(TenantId.fromUUID(tenantId2), getDevice(tenantId2, customerId2)); + savedDevicesUUID.add(deviceDao.save(TenantId.fromUUID(tenantId1), getDevice(tenantId1, customerId1, i)).getUuidId()); + savedDevicesUUID.add(deviceDao.save(TenantId.fromUUID(tenantId2), getDevice(tenantId2, customerId2, i + count / 2)).getUuidId()); } + return savedDevicesUUID; } - private Device getDevice(UUID tenantId, UUID customerID) { - return getDevice(tenantId, customerID, Uuids.timeBased()); + private Device getDevice(UUID tenantId, UUID customerID, int number) { + return getDevice(tenantId, customerID, Uuids.timeBased(), number); } - private Device getDevice(UUID tenantId, UUID customerID, UUID deviceId) { + private Device getDevice(UUID tenantId, UUID customerID, UUID deviceId, int number) { Device device = new Device(); device.setId(new DeviceId(deviceId)); device.setTenantId(TenantId.fromUUID(tenantId)); device.setCustomerId(new CustomerId(customerID)); - device.setName("SEARCH_TEXT"); + device.setName(PREFIX_FOR_DEVICE_NAME + number); + device.setDeviceProfileId(savedDeviceProfile.getId()); return device; } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java index 4b392c6030..eb5f004714 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java @@ -18,10 +18,11 @@ package org.thingsboard.server.dao.sql.event; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.springtestdbunit.annotation.DatabaseSetup; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; @@ -31,12 +32,12 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.event.EventDao; -import org.thingsboard.server.dao.service.AbstractServiceTest; import java.io.IOException; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ExecutionException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -51,43 +52,33 @@ import static org.thingsboard.server.common.data.DataConstants.STATS; @Slf4j public class JpaBaseEventDaoTest extends AbstractJpaDaoTest { - public static final long HOUR_MILLISECONDS = (long) 3.6e+6; @Autowired private EventDao eventDao; + UUID tenantId = Uuids.timeBased(); - @Test - public void testSaveIfNotExists() { - UUID eventId = Uuids.timeBased(); - UUID tenantId = Uuids.timeBased(); - UUID entityId = Uuids.timeBased(); - Event event = getEvent(eventId, tenantId, entityId); - Optional optEvent1 = eventDao.saveIfNotExists(event); - assertTrue("Optional is expected to be non-empty", optEvent1.isPresent()); - assertEquals(event, optEvent1.get()); - Optional optEvent2 = eventDao.saveIfNotExists(event); - assertFalse("Optional is expected to be empty", optEvent2.isPresent()); + @After + public void deleteEvents() { + List events = eventDao.find(TenantId.fromUUID(tenantId)); + for (Event event : events) { + eventDao.removeById(TenantId.fromUUID(tenantId), event.getUuidId()); + } } @Test - @DatabaseSetup("classpath:dbunit/event.xml") public void findEvent() { - UUID tenantId = UUID.fromString("be41c7a0-31f5-11e7-9cfd-2786e6aa2046"); - UUID entityId = UUID.fromString("be41c7a1-31f5-11e7-9cfd-2786e6aa2046"); - String eventType = STATS; - String eventUid = "be41c7a3-31f5-11e7-9cfd-2786e6aa2046"; - Event event = eventDao.findEvent(tenantId, new DeviceId(entityId), eventType, eventUid); - eventDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).stream().forEach(System.out::println); - assertNotNull("Event expected to be not null", event); - assertEquals("be41c7a2-31f5-11e7-9cfd-2786e6aa2046", event.getId().getId().toString()); + UUID entityId = Uuids.timeBased(); + Event savedEvent = eventDao.save(TenantId.fromUUID(tenantId), getEvent(entityId, tenantId, entityId)); + Event foundEvent = eventDao.findEvent(tenantId, new DeviceId(entityId), DataConstants.STATS, savedEvent.getUid()); + assertNotNull("Event expected to be not null", foundEvent); + assertEquals(savedEvent.getId(), foundEvent.getId()); } @Test - public void findEventsByEntityIdAndPageLink() { - UUID tenantId = Uuids.timeBased(); + public void findEventsByEntityIdAndPageLink() throws Exception { UUID entityId1 = Uuids.timeBased(); UUID entityId2 = Uuids.timeBased(); long startTime = System.currentTimeMillis(); - long endTime = createEventsTwoEntities(tenantId, entityId1, entityId2, startTime, 20); + long endTime = createEventsTwoEntities(tenantId, entityId1, entityId2, 20); TimePageLink pageLink1 = new TimePageLink(30); PageData events1 = eventDao.findEvents(tenantId, new DeviceId(entityId1), pageLink1); @@ -116,12 +107,11 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest { } @Test - public void findEventsByEntityIdAndEventTypeAndPageLink() { - UUID tenantId = Uuids.timeBased(); + public void findEventsByEntityIdAndEventTypeAndPageLink() throws Exception { UUID entityId1 = Uuids.timeBased(); UUID entityId2 = Uuids.timeBased(); long startTime = System.currentTimeMillis(); - long endTime = createEventsTwoEntitiesTwoTypes(tenantId, entityId1, entityId2, startTime, 20); + long endTime = createEventsTwoEntitiesTwoTypes(tenantId, entityId1, entityId2, 20); TimePageLink pageLink1 = new TimePageLink(30); PageData events1 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink1); @@ -141,30 +131,30 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest { pageLink4 = pageLink4.nextPageLink(); PageData events5 = eventDao.findEvents(tenantId, new DeviceId(entityId1), ALARM, pageLink4); - assertEquals(2, events5.getData().size()); + assertEquals(1, events5.getData().size()); } - private long createEventsTwoEntitiesTwoTypes(UUID tenantId, UUID entityId1, UUID entityId2, long startTime, int count) { + private long createEventsTwoEntitiesTwoTypes(UUID tenantId, UUID entityId1, UUID entityId2, int count) throws Exception { for (int i = 0; i < count / 2; i++) { String type = i % 2 == 0 ? STATS : ALARM; UUID eventId1 = Uuids.timeBased(); Event event1 = getEvent(eventId1, tenantId, entityId1, type); - eventDao.save(TenantId.fromUUID(tenantId), event1); + eventDao.saveAsync(event1).get(); UUID eventId2 = Uuids.timeBased(); Event event2 = getEvent(eventId2, tenantId, entityId2, type); - eventDao.save(TenantId.fromUUID(tenantId), event2); + eventDao.saveAsync(event2).get(); } return System.currentTimeMillis(); } - private long createEventsTwoEntities(UUID tenantId, UUID entityId1, UUID entityId2, long startTime, int count) { + private long createEventsTwoEntities(UUID tenantId, UUID entityId1, UUID entityId2, int count) throws Exception { for (int i = 0; i < count / 2; i++) { UUID eventId1 = Uuids.timeBased(); Event event1 = getEvent(eventId1, tenantId, entityId1); - eventDao.save(TenantId.fromUUID(tenantId), event1); + eventDao.saveAsync(event1).get(); UUID eventId2 = Uuids.timeBased(); Event event2 = getEvent(eventId2, tenantId, entityId2); - eventDao.save(TenantId.fromUUID(tenantId), event2); + eventDao.saveAsync(event2).get(); } return System.currentTimeMillis(); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponentTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponentTest.java new file mode 100644 index 0000000000..c3ac1f2c24 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponentTest.java @@ -0,0 +1,154 @@ +/** + * Copyright © 2016-2022 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.dao.sql.query; + +import org.junit.Before; +import org.junit.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.runner.RunWith; +import org.mockito.BDDMockito; +import org.mockito.Mockito; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.List; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.times; + +@RunWith(SpringRunner.class) +@ExtendWith(SpringExtension.class) +@ContextConfiguration(classes = DefaultQueryLogComponent.class) +@EnableConfigurationProperties +@TestPropertySource(properties = { + "sql.log_queries=true", + "sql.log_queries_threshold:2999" +}) + +public class DefaultQueryLogComponentTest { + + private TenantId tenantId; + private QueryContext ctx; + + @SpyBean + private DefaultQueryLogComponent queryLog; + + @Before + public void setUp() { + tenantId = new TenantId(UUID.fromString("97275c1c-9cf2-4d25-a68d-933031158f84")); + ctx = new QueryContext(new QuerySecurityContext(tenantId, null, EntityType.ALARM)); + } + + @Test + public void logQuery() { + + BDDMockito.willReturn("").given(queryLog).substituteParametersInSqlString("", ctx); + queryLog.logQuery(ctx, "", 3000); + + Mockito.verify(queryLog, times(1)).substituteParametersInSqlString("", ctx); + + } + + @Test + public void substituteParametersInSqlString_StringType() { + + String sql = "Select * from Table Where name = :name AND id = :id"; + String sqlToUse = "Select * from Table Where name = 'Mery''s' AND id = 'ID_1'"; + + ctx.addStringParameter("name", "Mery's"); + ctx.addStringParameter("id", "ID_1"); + + String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx); + assertEquals(sqlToUse, sqlToUseResult); + } + + @Test + public void substituteParametersInSqlString_DoubleLongType() { + + double sum = 0.00000021d; + long price = 100000; + String sql = "Select * from Table Where sum = :sum AND price = :price"; + String sqlToUse = "Select * from Table Where sum = 2.1E-7 AND price = 100000"; + + ctx.addDoubleParameter("sum", sum); + ctx.addLongParameter("price", price); + + String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx); + assertEquals(sqlToUse, sqlToUseResult); + } + + @Test + public void substituteParametersInSqlString_BooleanType() { + + String sql = "Select * from Table Where check = :check AND mark = :mark"; + String sqlToUse = "Select * from Table Where check = true AND mark = false"; + + ctx.addBooleanParameter("check", true); + ctx.addBooleanParameter("mark", false); + + String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx); + assertEquals(sqlToUse, sqlToUseResult); + } + + @Test + public void substituteParametersInSqlString_UuidType() { + + UUID guid = UUID.randomUUID(); + String sql = "Select * from Table Where guid = :guid"; + String sqlToUse = "Select * from Table Where guid = '" + guid + "'"; + + ctx.addUuidParameter("guid", guid); + + String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx); + assertEquals(sqlToUse, sqlToUseResult); + } + + @Test + public void substituteParametersInSqlString_StringListType() { + + List ids = List.of("ID_1'", "ID_2", "ID_3", "ID_4"); + + String sql = "Select * from Table Where id IN (:ids)"; + String sqlToUse = "Select * from Table Where id IN ('ID_1''', 'ID_2', 'ID_3', 'ID_4')"; + + ctx.addStringListParameter("ids", ids); + + String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx); + assertEquals(sqlToUse, sqlToUseResult); + } + + @Test + public void substituteParametersInSqlString_UuidListType() { + + List guids = List.of(UUID.fromString("634a8d03-6871-4e01-94d0-876bf3e67dff"), UUID.fromString("3adbb5b8-4dc6-4faf-80dc-681a7b518b5e"), UUID.fromString("63a50f0c-2058-4d1d-8f15-812eb7f84412")); + + String sql = "Select * from Table Where guid IN (:guids)"; + String sqlToUse = "Select * from Table Where guid IN ('634a8d03-6871-4e01-94d0-876bf3e67dff', '3adbb5b8-4dc6-4faf-80dc-681a7b518b5e', '63a50f0c-2058-4d1d-8f15-812eb7f84412')"; + + ctx.addUuidListParameter("guids", guids); + + String sqlToUseResult = queryLog.substituteParametersInSqlString(sql, ctx); + assertEquals(sqlToUse, sqlToUseResult); + } +} + diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java index 03d1eb01b1..916b572fba 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java @@ -66,36 +66,34 @@ public class JpaTenantDaoTest extends AbstractJpaDaoTest { @Test //@DatabaseSetup("classpath:dbunit/empty_dataset.xml") - public void testFindTenantsByRegion() { + public void testFindTenants() { createTenants(); - assertEquals(60, tenantDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); + assertEquals(30, tenantDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); PageLink pageLink = new PageLink(20, 0, "title"); - PageData tenants1 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", pageLink); + PageData tenants1 = tenantDao.findTenants(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink); assertEquals(20, tenants1.getData().size()); pageLink = pageLink.nextPageLink(); - PageData tenants2 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", + PageData tenants2 = tenantDao.findTenants(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink); assertEquals(10, tenants2.getData().size()); pageLink = pageLink.nextPageLink(); - PageData tenants3 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", + PageData tenants3 = tenantDao.findTenants(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink); assertEquals(0, tenants3.getData().size()); } private void createTenants() { for (int i = 0; i < 30; i++) { - createTenant("REGION_1", "TITLE", i); - createTenant("REGION_2", "TITLE", i); + createTenant("TITLE", i); } } - void createTenant(String region, String title, int index) { + void createTenant(String title, int index) { Tenant tenant = new Tenant(); tenant.setId(TenantId.fromUUID(Uuids.timeBased())); - tenant.setRegion(region); tenant.setTitle(title + "_" + index); tenant.setTenantProfileId(tenantProfile.getId()); createdTenants.add(tenantDao.save(TenantId.SYS_TENANT_ID, tenant)); diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDaoTest.java index 6105a6c16c..a93352176d 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDaoTest.java @@ -15,13 +15,17 @@ */ package org.thingsboard.server.dao.sql.user; -import com.github.springtestdbunit.annotation.DatabaseSetup; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.user.UserCredentialsDao; +import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -34,41 +38,66 @@ import static org.thingsboard.server.dao.service.AbstractServiceTest.SYSTEM_TENA */ public class JpaUserCredentialsDaoTest extends AbstractJpaDaoTest { + public static final String ACTIVATE_TOKEN = "ACTIVATE_TOKEN_0"; + public static final String RESET_TOKEN = "RESET_TOKEN_0"; + public static final int COUNT_USER_CREDENTIALS = 2; + List userCredentialsList; + UserCredentials neededUserCredentials; + @Autowired private UserCredentialsDao userCredentialsDao; + @Before + public void setUp() { + userCredentialsList = new ArrayList<>(); + for (int i=0; i userCredentials = userCredentialsDao.find(SYSTEM_TENANT_ID); - assertEquals(2, userCredentials.size()); + assertEquals(COUNT_USER_CREDENTIALS + 1, userCredentials.size()); } @Test - @DatabaseSetup("classpath:dbunit/user_credentials.xml") public void testFindByUserId() { - UserCredentials userCredentials = userCredentialsDao.findByUserId(SYSTEM_TENANT_ID, UUID.fromString("787827e6-27d7-11e7-93ae-92361f002671")); - assertNotNull(userCredentials); - assertEquals("4b9e010c-27d5-11e7-93ae-92361f002671", userCredentials.getId().toString()); - assertEquals(true, userCredentials.isEnabled()); - assertEquals("password", userCredentials.getPassword()); - assertEquals("ACTIVATE_TOKEN_2", userCredentials.getActivateToken()); - assertEquals("RESET_TOKEN_2", userCredentials.getResetToken()); + UserCredentials foundedUserCredentials = userCredentialsDao.findByUserId(SYSTEM_TENANT_ID, neededUserCredentials.getUserId().getId()); + assertNotNull(foundedUserCredentials); + assertEquals(neededUserCredentials, foundedUserCredentials); } @Test - @DatabaseSetup("classpath:dbunit/user_credentials.xml") public void testFindByActivateToken() { - UserCredentials userCredentials = userCredentialsDao.findByActivateToken(SYSTEM_TENANT_ID, "ACTIVATE_TOKEN_1"); - assertNotNull(userCredentials); - assertEquals("3ed10af0-27d5-11e7-93ae-92361f002671", userCredentials.getId().toString()); + UserCredentials foundedUserCredentials = userCredentialsDao.findByActivateToken(SYSTEM_TENANT_ID, ACTIVATE_TOKEN); + assertNotNull(foundedUserCredentials); + assertEquals(neededUserCredentials.getId(), foundedUserCredentials.getId()); } @Test - @DatabaseSetup("classpath:dbunit/user_credentials.xml") public void testFindByResetToken() { - UserCredentials userCredentials = userCredentialsDao.findByResetToken(SYSTEM_TENANT_ID, "RESET_TOKEN_2"); - assertNotNull(userCredentials); - assertEquals("4b9e010c-27d5-11e7-93ae-92361f002671", userCredentials.getId().toString()); + UserCredentials foundedUserCredentials = userCredentialsDao.findByResetToken(SYSTEM_TENANT_ID, RESET_TOKEN); + assertNotNull(foundedUserCredentials); + assertEquals(neededUserCredentials.getId(), foundedUserCredentials.getId()); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java index ac3c1a579a..880087f101 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/user/JpaUserDaoTest.java @@ -16,9 +16,11 @@ package org.thingsboard.server.dao.sql.user; import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.springtestdbunit.annotation.DatabaseSetup; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.User; @@ -32,7 +34,6 @@ import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.user.UserDao; -import java.io.IOException; import java.util.List; import java.util.UUID; @@ -45,35 +46,54 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; */ public class JpaUserDaoTest extends AbstractJpaDaoTest { + // it comes from the DefaultSystemDataLoaderService super class + final int COUNT_CREATED_USER = 1; + final int COUNT_SYSADMIN_USER = 90; + UUID tenantId; + UUID customerId; @Autowired private UserDao userDao; + @Before + public void setUp() { + tenantId = Uuids.timeBased(); + customerId = Uuids.timeBased(); + create30TenantAdminsAnd60CustomerUsers(tenantId, customerId); + } + + @After + public void tearDown() { + delete30TenantAdminsAnd60CustomerUsers(tenantId, customerId); + } + @Test - @DatabaseSetup("classpath:dbunit/user.xml") public void testFindAll() { List users = userDao.find(AbstractServiceTest.SYSTEM_TENANT_ID); - assertEquals(users.size(), 5); + assertEquals(users.size(), COUNT_CREATED_USER + COUNT_SYSADMIN_USER); } @Test - @DatabaseSetup("classpath:dbunit/user.xml") - public void testFindByEmail() { - User user = userDao.findByEmail(AbstractServiceTest.SYSTEM_TENANT_ID,"sysadm@thingsboard.org"); - assertNotNull("User is expected to be not null", user); - assertEquals("9cb58ba0-27c1-11e7-93ae-92361f002671", user.getId().toString()); - assertEquals("c97ea14e-27c1-11e7-93ae-92361f002671", user.getTenantId().toString()); - assertEquals("cdf9c79e-27c1-11e7-93ae-92361f002671", user.getCustomerId().toString()); - assertEquals(Authority.SYS_ADMIN, user.getAuthority()); - assertEquals("John", user.getFirstName()); - assertEquals("Doe", user.getLastName()); + public void testFindByEmail() throws JsonProcessingException { + User user = new User(); + user.setId(new UserId(UUID.randomUUID())); + user.setTenantId(TenantId.fromUUID(UUID.randomUUID())); + user.setCustomerId(new CustomerId(UUID.randomUUID())); + user.setEmail("user@thingsboard.org"); + user.setFirstName("Jackson"); + user.setLastName("Roberts"); + ObjectMapper mapper = new ObjectMapper(); + String additionalInfo = "{\"key\":\"value-100\"}"; + JsonNode jsonNode = mapper.readTree(additionalInfo); + user.setAdditionalInfo(jsonNode); + userDao.save(AbstractServiceTest.SYSTEM_TENANT_ID, user); + assertEquals(1 + COUNT_SYSADMIN_USER + COUNT_CREATED_USER, userDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); + User savedUser = userDao.findByEmail(AbstractServiceTest.SYSTEM_TENANT_ID, "user@thingsboard.org"); + assertNotNull(savedUser); + assertEquals(additionalInfo, savedUser.getAdditionalInfo().toString()); } @Test - @DatabaseSetup("classpath:dbunit/empty_dataset.xml") public void testFindTenantAdmins() { - UUID tenantId = Uuids.timeBased(); - UUID customerId = Uuids.timeBased(); - create30Adminsand60Users(tenantId, customerId); PageLink pageLink = new PageLink(20); PageData tenantAdmins1 = userDao.findTenantAdmins(tenantId, pageLink); assertEquals(20, tenantAdmins1.getData().size()); @@ -88,11 +108,7 @@ public class JpaUserDaoTest extends AbstractJpaDaoTest { } @Test - @DatabaseSetup("classpath:dbunit/empty_dataset.xml") public void testFindCustomerUsers() { - UUID tenantId = Uuids.timeBased(); - UUID customerId = Uuids.timeBased(); - create30Adminsand60Users(tenantId, customerId); PageLink pageLink = new PageLink(40); PageData customerUsers1 = userDao.findCustomerUsers(tenantId, customerId, pageLink); assertEquals(40, customerUsers1.getData().size()); @@ -106,28 +122,7 @@ public class JpaUserDaoTest extends AbstractJpaDaoTest { assertEquals(0, customerUsers3.getData().size()); } - @Test - @DatabaseSetup("classpath:dbunit/user.xml") - public void testSave() throws IOException { - User user = new User(); - user.setId(new UserId(UUID.fromString("cd481534-27cc-11e7-93ae-92361f002671"))); - user.setTenantId(TenantId.fromUUID(UUID.fromString("1edcb2c6-27cb-11e7-93ae-92361f002671"))); - user.setCustomerId(new CustomerId(UUID.fromString("51477cb4-27cb-11e7-93ae-92361f002671"))); - user.setEmail("user@thingsboard.org"); - user.setFirstName("Jackson"); - user.setLastName("Roberts"); - ObjectMapper mapper = new ObjectMapper(); - String additionalInfo = "{\"key\":\"value-100\"}"; - JsonNode jsonNode = mapper.readTree(additionalInfo); - user.setAdditionalInfo(jsonNode); - userDao.save(AbstractServiceTest.SYSTEM_TENANT_ID,user); - assertEquals(6, userDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); - User savedUser = userDao.findByEmail(AbstractServiceTest.SYSTEM_TENANT_ID,"user@thingsboard.org"); - assertNotNull(savedUser); - assertEquals(additionalInfo, savedUser.getAdditionalInfo().toString()); - } - - private void create30Adminsand60Users(UUID tenantId, UUID customerId) { + private void create30TenantAdminsAnd60CustomerUsers(UUID tenantId, UUID customerId) { // Create 30 tenant admins and 60 customer users for (int i = 0; i < 30; i++) { saveUser(tenantId, NULL_UUID); @@ -150,6 +145,14 @@ public class JpaUserDaoTest extends AbstractJpaDaoTest { String idString = id.toString(); String email = idString.substring(0, idString.indexOf('-')) + "@thingsboard.org"; user.setEmail(email); - userDao.save(AbstractServiceTest.SYSTEM_TENANT_ID,user); + userDao.save(AbstractServiceTest.SYSTEM_TENANT_ID, user); + } + + private void delete30TenantAdminsAnd60CustomerUsers(UUID tenantId, UUID customerId) { + List data = userDao.findCustomerUsers(tenantId, customerId, new PageLink(60)).getData(); + data.addAll(userDao.findTenantAdmins(tenantId, new PageLink(30)).getData()); + for (User user : data) { + userDao.removeById(user.getTenantId(), user.getUuidId()); + } } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java index 9442a2b4ea..1dcc8f0bbe 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java @@ -15,40 +15,69 @@ */ package org.thingsboard.server.dao.sql.widget; -import com.github.springtestdbunit.annotation.DatabaseSetup; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.dao.AbstractJpaDaoTest; import org.thingsboard.server.dao.widget.WidgetTypeDao; +import java.util.ArrayList; import java.util.List; -import java.util.UUID; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; /** * Created by Valerii Sosliuk on 4/30/2017. */ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest { + final String BUNDLE_ALIAS = "BUNDLE_ALIAS"; + final int WIDGET_TYPE_COUNT = 3; + List widgetTypeList; + @Autowired private WidgetTypeDao widgetTypeDao; + @Before + public void setUp() { + widgetTypeList = new ArrayList<>(); + for (int i = 0; i < WIDGET_TYPE_COUNT; i++) { + widgetTypeList.add(createAndSaveWidgetType(i)); + } + } + + WidgetType createAndSaveWidgetType(int number) { + WidgetTypeDetails widgetType = new WidgetTypeDetails(); + widgetType.setTenantId(TenantId.SYS_TENANT_ID); + widgetType.setName("WIDGET_TYPE_" + number); + widgetType.setAlias("ALIAS_" + number); + widgetType.setBundleAlias(BUNDLE_ALIAS); + return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); + } + + @After + public void deleteAllWidgetType() { + for (WidgetType widgetType : widgetTypeList) { + widgetTypeDao.removeById(TenantId.SYS_TENANT_ID, widgetType.getUuidId()); + } + } + @Test - @DatabaseSetup(("classpath:dbunit/widget_type.xml")) public void testFindByTenantIdAndBundleAlias() { - UUID tenantId = UUID.fromString("2b7e4c90-2dfe-11e7-94aa-f7f6dbfb4833"); - List widgetTypes = widgetTypeDao.findWidgetTypesByTenantIdAndBundleAlias(tenantId, "BUNDLE_ALIAS_1"); - assertEquals(3, widgetTypes.size()); + List widgetTypes = widgetTypeDao.findWidgetTypesByTenantIdAndBundleAlias(TenantId.SYS_TENANT_ID.getId(), BUNDLE_ALIAS); + assertEquals(WIDGET_TYPE_COUNT, widgetTypes.size()); } @Test - @DatabaseSetup(("classpath:dbunit/widget_type.xml")) public void testFindByTenantIdAndBundleAliasAndAlias() { - UUID tenantId = UUID.fromString("2b7e4c90-2dfe-11e7-94aa-f7f6dbfb4833"); - WidgetType widgetType = widgetTypeDao.findByTenantIdBundleAliasAndAlias(tenantId, "BUNDLE_ALIAS_1", "ALIAS3"); - UUID id = UUID.fromString("2b7e4c93-2dfe-11e7-94aa-f7f6dbfb4833"); - assertEquals(id, widgetType.getId().getId()); + WidgetType result = widgetTypeList.get(0); + assertNotNull(result); + WidgetType widgetType = widgetTypeDao.findByTenantIdBundleAliasAndAlias(TenantId.SYS_TENANT_ID.getId(), BUNDLE_ALIAS, "ALIAS_0"); + assertEquals(result.getId(), widgetType.getId()); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java index 1d9d18edd1..8b138b11b6 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetsBundleDaoTest.java @@ -16,9 +16,7 @@ package org.thingsboard.server.dao.sql.widget; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.github.springtestdbunit.annotation.DatabaseOperation; -import com.github.springtestdbunit.annotation.DatabaseSetup; -import com.github.springtestdbunit.annotation.DatabaseTearDown; +import org.junit.After; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.id.TenantId; @@ -27,72 +25,79 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.AbstractJpaDaoTest; -import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.widget.WidgetsBundleDao; import java.util.List; import java.util.UUID; import static org.junit.Assert.assertEquals; -import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; /** * Created by Valerii Sosliuk on 4/23/2017. */ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { + List widgetsBundles; @Autowired private WidgetsBundleDao widgetsBundleDao; + @After + public void tearDown() { + for (WidgetsBundle widgetsBundle : widgetsBundles) { + widgetsBundleDao.removeById(widgetsBundle.getTenantId(), widgetsBundle.getUuidId()); + } + + } + @Test - @DatabaseSetup(value = "classpath:dbunit/widgets_bundle.xml",type= DatabaseOperation.CLEAN_INSERT) - @DatabaseTearDown(value = "classpath:dbunit/widgets_bundle.xml", type= DatabaseOperation.DELETE_ALL) public void testFindAll() { - assertEquals(7, widgetsBundleDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); + createSystemWidgetBundles(7, "WB_"); + widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID); + assertEquals(7, widgetsBundles.size()); } @Test - @DatabaseSetup(value = "classpath:dbunit/widgets_bundle.xml",type= DatabaseOperation.CLEAN_INSERT) - @DatabaseTearDown(value = "classpath:dbunit/widgets_bundle.xml", type= DatabaseOperation.DELETE_ALL) public void testFindWidgetsBundleByTenantIdAndAlias() { + createSystemWidgetBundles(1, "WB_"); WidgetsBundle widgetsBundle = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias( - UUID.fromString("250aca8e-2825-11e7-93ae-92361f002671"), "WB3"); - assertEquals("44e6af4e-2825-11e7-93ae-92361f002671", widgetsBundle.getId().toString()); + TenantId.SYS_TENANT_ID.getId(), "WB_" + 0); + widgetsBundles = List.of(widgetsBundle); + assertEquals("WB_" + 0, widgetsBundle.getAlias()); } @Test - @DatabaseSetup(value = "classpath:dbunit/widgets_bundle.xml", type= DatabaseOperation.DELETE_ALL) public void testFindSystemWidgetsBundles() { createSystemWidgetBundles(30, "WB_"); - assertEquals(30, widgetsBundleDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); + widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID); + assertEquals(30, widgetsBundles.size()); // Get first page PageLink pageLink = new PageLink(10, 0, "WB"); - PageData widgetsBundles1 = widgetsBundleDao.findSystemWidgetsBundles(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink); + PageData widgetsBundles1 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, pageLink); assertEquals(10, widgetsBundles1.getData().size()); // Get next page pageLink = pageLink.nextPageLink(); - PageData widgetsBundles2 = widgetsBundleDao.findSystemWidgetsBundles(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink); + PageData widgetsBundles2 = widgetsBundleDao.findSystemWidgetsBundles(TenantId.SYS_TENANT_ID, pageLink); assertEquals(10, widgetsBundles2.getData().size()); } @Test - @DatabaseSetup(value = "classpath:dbunit/widgets_bundle.xml", type= DatabaseOperation.DELETE_ALL) public void testFindWidgetsBundlesByTenantId() { UUID tenantId1 = Uuids.timeBased(); UUID tenantId2 = Uuids.timeBased(); // Create a bunch of widgetBundles - for (int i= 0; i < 10; i++) { + for (int i = 0; i < 10; i++) { createWidgetBundles(3, tenantId1, "WB1_"); createWidgetBundles(5, tenantId2, "WB2_"); createSystemWidgetBundles(10, "WB_SYS_"); } - assertEquals(180, widgetsBundleDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); + widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID); + assertEquals(180, widgetsBundleDao.find(TenantId.SYS_TENANT_ID).size()); - PageLink pageLink1 = new PageLink(40, 0, "WB"); + PageLink pageLink1 = new PageLink(40, 0, "WB"); PageData widgetsBundles1 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId1, pageLink1); assertEquals(30, widgetsBundles1.getData().size()); - PageLink pageLink2 = new PageLink(40, 0, "WB"); + PageLink pageLink2 = new PageLink(40, 0, "WB"); PageData widgetsBundles2 = widgetsBundleDao.findTenantWidgetsBundlesByTenantId(tenantId2, pageLink2); assertEquals(40, widgetsBundles2.getData().size()); @@ -102,18 +107,19 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { } @Test - @DatabaseSetup(value = "classpath:dbunit/widgets_bundle.xml", type= DatabaseOperation.DELETE_ALL) public void testFindAllWidgetsBundlesByTenantId() { UUID tenantId1 = Uuids.timeBased(); UUID tenantId2 = Uuids.timeBased(); // Create a bunch of widgetBundles - for (int i= 0; i < 10; i++) { - createWidgetBundles( 5, tenantId1,"WB1_"); + for (int i = 0; i < 10; i++) { + createWidgetBundles(5, tenantId1, "WB1_"); createWidgetBundles(3, tenantId2, "WB2_"); createSystemWidgetBundles(2, "WB_SYS_"); } + widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID); + assertEquals(100, widgetsBundleDao.find(TenantId.SYS_TENANT_ID).size()); - PageLink pageLink = new PageLink(30, 0, "WB"); + PageLink pageLink = new PageLink(30, 0, "WB"); PageData widgetsBundles1 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId1, pageLink); assertEquals(30, widgetsBundles1.getData().size()); @@ -131,13 +137,12 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { } @Test - @DatabaseSetup("classpath:dbunit/empty_dataset.xml") - @DatabaseTearDown(value = "classpath:dbunit/empty_dataset.xml", type= DatabaseOperation.DELETE_ALL) public void testSearchTextNotFound() { UUID tenantId = Uuids.timeBased(); createWidgetBundles(5, tenantId, "ABC_"); createSystemWidgetBundles(5, "SYS_"); - + widgetsBundles = widgetsBundleDao.find(TenantId.SYS_TENANT_ID); + assertEquals(10, widgetsBundleDao.find(TenantId.SYS_TENANT_ID).size()); PageLink textPageLink = new PageLink(30, 0, "TEXT_NOT_FOUND"); PageData widgetsBundles4 = widgetsBundleDao.findAllTenantWidgetsBundlesByTenantId(tenantId, textPageLink); assertEquals(0, widgetsBundles4.getData().size()); @@ -150,9 +155,10 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { widgetsBundle.setTitle(prefix + i); widgetsBundle.setId(new WidgetsBundleId(Uuids.timeBased())); widgetsBundle.setTenantId(TenantId.fromUUID(tenantId)); - widgetsBundleDao.save(AbstractServiceTest.SYSTEM_TENANT_ID, widgetsBundle); + widgetsBundleDao.save(TenantId.SYS_TENANT_ID, widgetsBundle); } } + private void createSystemWidgetBundles(int count, String prefix) { for (int i = 0; i < count; i++) { WidgetsBundle widgetsBundle = new WidgetsBundle(); @@ -160,7 +166,7 @@ public class JpaWidgetsBundleDaoTest extends AbstractJpaDaoTest { widgetsBundle.setTitle(prefix + i); widgetsBundle.setTenantId(TenantId.SYS_TENANT_ID); widgetsBundle.setId(new WidgetsBundleId(Uuids.timeBased())); - widgetsBundleDao.save(AbstractServiceTest.SYSTEM_TENANT_ID, widgetsBundle); + widgetsBundleDao.save(TenantId.SYS_TENANT_ID, widgetsBundle); } } } diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index a257c8fbce..5d02ae84d5 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -60,4 +60,47 @@ database.ts_max_intervals=700 sql.remove_null_chars=true -edges.enabled=true +# Edge disabled to speed up the context init. Will be enabled by @TestPropertySource in respective tests +edges.enabled=false + +# Transports disabled to speed up the context init. Particular transport will be enabled with @TestPropertySource in respective tests +transport.http.enabled=false +transport.mqtt.enabled=false +transport.coap.enabled=false +transport.lwm2m.enabled=false +transport.snmp.enabled=false + +# Low latency settings to perform tests as fast as possible +sql.attributes.batch_max_delay=5 +sql.attributes.batch_threads=2 +sql.ts.batch_max_delay=5 +sql.ts.batch_threads=2 +sql.ts_latest.batch_max_delay=5 +sql.ts_latest.batch_threads=2 +sql.events.batch_max_delay=5 +sql.events.batch_threads=2 +actors.system.tenant_dispatcher_pool_size=4 +actors.system.device_dispatcher_pool_size=8 +actors.system.rule_dispatcher_pool_size=12 +transport.sessions.report_timeout=10000 +queue.transport_api.request_poll_interval=5 +queue.transport_api.response_poll_interval=5 +queue.transport.poll_interval=5 +queue.core.poll-interval=5 +queue.core.partitions=2 +queue.rule-engine.poll-interval=5 +queue.rule-engine.queues[0].poll-interval=5 +queue.rule-engine.queues[0].partitions=2 +queue.rule-engine.queues[0].processing-strategy.retries=1 +queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 +queue.rule-engine.queues[1].poll-interval=5 +queue.rule-engine.queues[1].partitions=2 +queue.rule-engine.queues[1].processing-strategy.retries=1 +queue.rule-engine.queues[1].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[1].processing-strategy.max-pause-between-retries=0 +queue.rule-engine.queues[2].poll-interval=5 +queue.rule-engine.queues[2].partitions=2 +queue.rule-engine.queues[2].processing-strategy.retries=1 +queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0 diff --git a/dao/src/test/resources/dbunit/device_credentials.xml b/dao/src/test/resources/dbunit/device_credentials.xml deleted file mode 100644 index 5f813e26d9..0000000000 --- a/dao/src/test/resources/dbunit/device_credentials.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/dao/src/test/resources/dbunit/empty_dataset.xml b/dao/src/test/resources/dbunit/empty_dataset.xml deleted file mode 100644 index 5ed00ba028..0000000000 --- a/dao/src/test/resources/dbunit/empty_dataset.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/dao/src/test/resources/dbunit/event.xml b/dao/src/test/resources/dbunit/event.xml deleted file mode 100644 index 66b03faa5e..0000000000 --- a/dao/src/test/resources/dbunit/event.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/dao/src/test/resources/dbunit/rule.xml b/dao/src/test/resources/dbunit/rule.xml deleted file mode 100644 index dfe1054785..0000000000 --- a/dao/src/test/resources/dbunit/rule.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/dao/src/test/resources/dbunit/user.xml b/dao/src/test/resources/dbunit/user.xml deleted file mode 100644 index 417ef0dd4b..0000000000 --- a/dao/src/test/resources/dbunit/user.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/dao/src/test/resources/dbunit/user_credentials.xml b/dao/src/test/resources/dbunit/user_credentials.xml deleted file mode 100644 index 7d16de8573..0000000000 --- a/dao/src/test/resources/dbunit/user_credentials.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - \ No newline at end of file diff --git a/dao/src/test/resources/dbunit/widget_type.xml b/dao/src/test/resources/dbunit/widget_type.xml deleted file mode 100644 index 49664d32b3..0000000000 --- a/dao/src/test/resources/dbunit/widget_type.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/dao/src/test/resources/dbunit/widgets_bundle.xml b/dao/src/test/resources/dbunit/widgets_bundle.xml deleted file mode 100644 index 3a66657cf5..0000000000 --- a/dao/src/test/resources/dbunit/widgets_bundle.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties index b81f5db8ac..293ec2235d 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -17,3 +17,14 @@ spring.datasource.password=postgres spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver spring.datasource.hikari.maximumPoolSize = 50 + +queue.rule-engine.queues[0].name=Main +queue.rule-engine.queues[0].topic=tb_rule_engine.main +queue.rule-engine.queues[0].poll-interval=5 +queue.rule-engine.queues[0].partitions=2 +queue.rule-engine.queues[0].pack-processing-timeout=3000 +queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES +queue.rule-engine.queues[0].processing-strategy.retries=1 +queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 +queue.rule-engine.queues[0].submit-strategy.type=BURST diff --git a/dao/src/test/resources/sql-test.properties b/dao/src/test/resources/sql-test.properties index d2add71eea..52033b3b27 100644 --- a/dao/src/test/resources/sql-test.properties +++ b/dao/src/test/resources/sql-test.properties @@ -45,10 +45,13 @@ queue.rule-engine.pack-processing-timeout=3000 queue.rule-engine.queues[0].name=Main queue.rule-engine.queues[0].topic=tb_rule_engine.main -queue.rule-engine.queues[0].poll-interval=25 -queue.rule-engine.queues[0].partitions=3 +queue.rule-engine.queues[0].poll-interval=5 +queue.rule-engine.queues[0].partitions=2 queue.rule-engine.queues[0].pack-processing-timeout=3000 queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES +queue.rule-engine.queues[0].processing-strategy.retries=1 +queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0 +queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0 queue.rule-engine.queues[0].submit-strategy.type=BURST sql.log_entity_queries=true diff --git a/dao/src/test/resources/sql/system-test-psql.sql b/dao/src/test/resources/sql/system-test-psql.sql index 16dcb8c3ae..177f838bb1 100644 --- a/dao/src/test/resources/sql/system-test-psql.sql +++ b/dao/src/test/resources/sql/system-test-psql.sql @@ -1,2 +1,2 @@ --PostgreSQL specific truncate to fit constraints -TRUNCATE TABLE device_credentials, device, device_profile, rule_node_state, rule_node, rule_chain; \ No newline at end of file +TRUNCATE TABLE device_credentials, device, device_profile, ota_package, rule_node_state, rule_node, rule_chain; \ No newline at end of file diff --git a/dao/src/test/resources/sql/system-test.sql b/dao/src/test/resources/sql/system-test.sql index 68a0e0efa3..d673548fb0 100644 --- a/dao/src/test/resources/sql/system-test.sql +++ b/dao/src/test/resources/sql/system-test.sql @@ -1,6 +1,6 @@ TRUNCATE TABLE device_credentials; TRUNCATE TABLE device; -TRUNCATE TABLE device_profile; +TRUNCATE TABLE device_profile CASCADE; TRUNCATE TABLE rule_node_state; TRUNCATE TABLE rule_node; TRUNCATE TABLE rule_chain; \ No newline at end of file diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 2ac9b95d71..29279b0bed 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/black-box-tests/src/test/resources/logback.xml b/msa/black-box-tests/src/test/resources/logback.xml new file mode 100644 index 0000000000..cdf87aab92 --- /dev/null +++ b/msa/black-box-tests/src/test/resources/logback.xml @@ -0,0 +1,32 @@ + + + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index d206de51d5..6cbfc1261d 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "3.3.4", + "version": "3.4.0", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 82dc0c835d..5532a70b78 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/pom.xml b/msa/pom.xml index c4e4b56011..106f2be2a3 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 77e39fa851..f451f84b4e 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index dd811e6393..a1aa1c0e16 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index ee3dc5a0fe..2e633016bf 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index eeab37c185..76cd3e9725 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index 74ca1e158b..51a019f45f 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index a73bc39146..5f84fc3693 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 7c855e717d..ca73383c39 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index 3daf08bf4c..4156ee9b90 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index c55bcbc0a1..1cd252e0de 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "3.3.4", + "version": "3.4.0", "description": "ThingsBoard Web UI Microservice", "main": "server.js", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 9219ff5359..f04b339a8e 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 7bb1cf6ed7..662d677f8b 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard netty-mqtt - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT jar Netty MQTT Client diff --git a/pom.xml b/pom.xml index d572580132..02297203e6 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT pom Thingsboard @@ -39,13 +39,13 @@ 1.3.2 2.3.2 2.3.2 - 2.3.12.RELEASE - 2.3.9.RELEASE - 5.2.16.RELEASE - 5.2.11.RELEASE - 5.4.7 - 2.4.3 - 3.3.0 + 2.5.12 + 2.5.10 + 5.3.18 + 5.5.10 + 5.6.2 + 2.5.10 + 3.7.1 0.7.0 1.7.32 2.17.1 @@ -65,7 +65,7 @@ 4.5.13 4.4.14 2.8.1 - 2.12.1 + 2.13.2 1.3.4 2.2.6 3.0.0 @@ -79,8 +79,8 @@ 1.42.1 1.18.18 1.2.4 - 4.1.72.Final - 2.0.46.Final + 4.1.75.Final + 2.0.51.Final 1.7.0 4.8.0 2.19.1 @@ -112,7 +112,7 @@ 1.4.3 1.9.4 3.2.2 - 1.5.2 + 1.8.3 1.0.3TB 3.4.0 8.17.0 @@ -127,13 +127,14 @@ 2.7.2 2.6.1 1.5.2 - 5.6.3 + 5.7.2 2.6.0 1.3.0 1.2.7 1.16.0 1.12 + 3.0.0 @@ -1875,6 +1876,11 @@ ${zeroturnaround.version} test + + org.opensmpp + opensmpp-core + ${opensmpp.version} + diff --git a/rest-client/pom.xml b/rest-client/pom.xml index e6eaee5dc0..40c07c965f 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard rest-client diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 1c511bd141..44ee42a487 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 7b9d396d2d..8a30088dcd 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index f06b4a7395..2606fc32d4 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT rule-engine org.thingsboard.rule-engine diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index b67662dfe7..707aa53cd2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -23,6 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.EnumUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; @@ -122,40 +123,66 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode createNewAlarm(TbContext ctx, TbMsg msg, Alarm msgAlarm) { - ListenableFuture asyncAlarm; - if (msgAlarm != null) { - asyncAlarm = Futures.immediateFuture(msgAlarm); - } else { + ListenableFuture asyncDetails; + boolean buildDetails = !config.isUseMessageAlarmData() || config.isOverwriteAlarmDetails(); + if (buildDetails) { ctx.logJsEvalRequest(); - asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null), - details -> { - ctx.logJsEvalResponse(); - return buildAlarm(msg, details, ctx.getTenantId()); - }, MoreExecutors.directExecutor()); + asyncDetails = buildAlarmDetails(ctx, msg, null); + } else { + asyncDetails = Futures.immediateFuture(null); } + ListenableFuture asyncAlarm = Futures.transform(asyncDetails, details -> { + if (buildDetails) { + ctx.logJsEvalResponse(); + } + Alarm newAlarm; + if (msgAlarm != null) { + newAlarm = msgAlarm; + if (buildDetails) { + newAlarm.setDetails(details); + } + } else { + newAlarm = buildAlarm(msg, details, ctx.getTenantId()); + } + return newAlarm; + }, MoreExecutors.directExecutor()); ListenableFuture asyncCreated = Futures.transform(asyncAlarm, alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor()); return Futures.transform(asyncCreated, alarm -> new TbAlarmResult(true, false, false, alarm), MoreExecutors.directExecutor()); } private ListenableFuture updateAlarm(TbContext ctx, TbMsg msg, Alarm existingAlarm, Alarm msgAlarm) { - ctx.logJsEvalRequest(); - ListenableFuture asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, existingAlarm.getDetails()), (Function) details -> { - ctx.logJsEvalResponse(); + ListenableFuture asyncDetails; + boolean buildDetails = !config.isUseMessageAlarmData() || config.isOverwriteAlarmDetails(); + if (buildDetails) { + ctx.logJsEvalRequest(); + asyncDetails = buildAlarmDetails(ctx, msg, existingAlarm.getDetails()); + } else { + asyncDetails = Futures.immediateFuture(null); + } + ListenableFuture asyncUpdated = Futures.transform(asyncDetails, (Function) details -> { + if (buildDetails) { + ctx.logJsEvalResponse(); + } if (msgAlarm != null) { existingAlarm.setSeverity(msgAlarm.getSeverity()); existingAlarm.setPropagate(msgAlarm.isPropagate()); existingAlarm.setPropagateToOwner(msgAlarm.isPropagateToOwner()); existingAlarm.setPropagateToTenant(msgAlarm.isPropagateToTenant()); existingAlarm.setPropagateRelationTypes(msgAlarm.getPropagateRelationTypes()); + if (buildDetails) { + existingAlarm.setDetails(details); + } else { + existingAlarm.setDetails(msgAlarm.getDetails()); + } } else { existingAlarm.setSeverity(processAlarmSeverity(msg)); existingAlarm.setPropagate(config.isPropagate()); existingAlarm.setPropagateToOwner(config.isPropagateToOwner()); existingAlarm.setPropagateToTenant(config.isPropagateToTenant()); existingAlarm.setPropagateRelationTypes(relationTypes); + existingAlarm.setDetails(details); } - existingAlarm.setDetails(details); existingAlarm.setEndTs(System.currentTimeMillis()); return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm); }, ctx.getDbCallbackExecutor()); @@ -171,6 +198,8 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode relationTypes; @@ -44,8 +45,10 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura configuration.setPropagateToOwner(false); configuration.setPropagateToTenant(false); configuration.setUseMessageAlarmData(false); + configuration.setOverwriteAlarmDetails(false); configuration.setRelationTypes(Collections.emptyList()); configuration.setDynamicSeverity(false); return configuration; } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java index e3802bcdec..431d63a674 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; @@ -30,8 +31,6 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import static org.thingsboard.common.util.DonAsynchron.withCallback; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -49,15 +48,22 @@ public class TbLogNode implements TbNode { private TbLogNodeConfiguration config; private ScriptEngine jsEngine; + private boolean standard; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbLogNodeConfiguration.class); - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript()); + this.standard = new TbLogNodeConfiguration().defaultConfiguration().getJsScript().equals(config.getJsScript()); + this.jsEngine = this.standard ? null : ctx.createJsScriptEngine(config.getJsScript()); } @Override public void onMsg(TbContext ctx, TbMsg msg) { + if (standard) { + logStandard(ctx, msg); + return; + } + ctx.logJsEvalRequest(); Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback() { @Override @@ -75,6 +81,17 @@ public class TbLogNode implements TbNode { }, MoreExecutors.directExecutor()); //usually js responses runs on js callback executor } + void logStandard(TbContext ctx, TbMsg msg) { + log.info(toLogMessage(msg)); + ctx.tellSuccess(msg); + } + + String toLogMessage(TbMsg msg) { + return "\n" + + "Incoming message:\n" + msg.getData() + "\n" + + "Incoming metadata:\n" + JacksonUtil.toString(msg.getMetaData().getData()); + } + @Override public void destroy() { if (jsEngine != null) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java index a176046c19..453ade72a7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java @@ -26,7 +26,7 @@ public class TbLogNodeConfiguration implements NodeConfiguration { @Override public TbLogNodeConfiguration defaultConfiguration() { TbLogNodeConfiguration configuration = new TbLogNodeConfiguration(); - configuration.setJsScript("return 'Incoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"); + configuration.setJsScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java new file mode 100644 index 0000000000..eb30a236af --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -0,0 +1,178 @@ +/** + * Copyright © 2016-2022 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.rule.engine.edge; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.session.SessionMsgType; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Slf4j +public abstract class AbstractTbMsgPushNode implements TbNode { + + protected T config; + + private static final String SCOPE = "scope"; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, getConfigClazz()); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + if (getIgnoredMessageSource().equalsIgnoreCase(msg.getMetaData().getValue(DataConstants.MSG_SOURCE_KEY))) { + log.debug("Ignoring msg from the {}, msg [{}]", getIgnoredMessageSource(), msg); + ctx.ack(msg); + return; + } + if (isSupportedOriginator(msg.getOriginator().getEntityType())) { + if (isSupportedMsgType(msg.getType())) { + processMsg(ctx, msg); + } else { + String errMsg = String.format("Unsupported msg type %s", msg.getType()); + log.debug(errMsg); + ctx.tellFailure(msg, new RuntimeException(errMsg)); + } + } else { + String errMsg = String.format("Unsupported originator type %s", msg.getOriginator().getEntityType()); + log.debug(errMsg); + ctx.tellFailure(msg, new RuntimeException(errMsg)); + } + } + + @Override + public void destroy() { + } + + protected S buildEvent(TbMsg msg, TbContext ctx) { + String msgType = msg.getType(); + if (DataConstants.ALARM.equals(msgType)) { + return buildEvent(ctx.getTenantId(), EdgeEventActionType.ADDED, getUUIDFromMsgData(msg), getAlarmEventType(), null); + } else { + EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType); + Map entityBody = new HashMap<>(); + Map metadata = msg.getMetaData().getData(); + JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData()); + switch (actionType) { + case ATTRIBUTES_UPDATED: + case POST_ATTRIBUTES: + entityBody.put("kv", dataJson); + entityBody.put(SCOPE, getScope(metadata)); + if (EdgeEventActionType.POST_ATTRIBUTES.equals(actionType)) { + entityBody.put("isPostAttributes", true); + } + break; + case ATTRIBUTES_DELETED: + List keys = JacksonUtil.convertValue(dataJson.get("attributes"), new TypeReference<>() {}); + entityBody.put("keys", keys); + entityBody.put(SCOPE, getScope(metadata)); + break; + case TIMESERIES_UPDATED: + entityBody.put("data", dataJson); + entityBody.put("ts", msg.getMetaDataTs()); + break; + } + return buildEvent(ctx.getTenantId(), + actionType, + msg.getOriginator().getId(), + getEventTypeByEntityType(msg.getOriginator().getEntityType()), + JacksonUtil.valueToTree(entityBody)); + } + } + + abstract S buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId, U eventType, JsonNode entityBody); + + abstract U getEventTypeByEntityType(EntityType entityType); + + abstract U getAlarmEventType(); + + abstract String getIgnoredMessageSource(); + + abstract protected Class getConfigClazz(); + + abstract void processMsg(TbContext ctx, TbMsg msg); + + protected UUID getUUIDFromMsgData(TbMsg msg) { + JsonNode data = JacksonUtil.toJsonNode(msg.getData()).get("id"); + String id = JacksonUtil.convertValue(data.get("id"), String.class); + return UUID.fromString(id); + } + + protected String getScope(Map metadata) { + String scope = metadata.get(SCOPE); + if (StringUtils.isEmpty(scope)) { + scope = config.getScope(); + } + return scope; + } + + protected EdgeEventActionType getEdgeEventActionTypeByMsgType(String msgType) { + EdgeEventActionType actionType; + if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType) + || DataConstants.TIMESERIES_UPDATED.equals(msgType)) { + actionType = EdgeEventActionType.TIMESERIES_UPDATED; + } else if (DataConstants.ATTRIBUTES_UPDATED.equals(msgType)) { + actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; + } else if (SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { + actionType = EdgeEventActionType.POST_ATTRIBUTES; + } else { + actionType = EdgeEventActionType.ATTRIBUTES_DELETED; + } + return actionType; + } + + protected boolean isSupportedMsgType(String msgType) { + return SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType) + || SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType) + || DataConstants.ATTRIBUTES_UPDATED.equals(msgType) + || DataConstants.ATTRIBUTES_DELETED.equals(msgType) + || DataConstants.TIMESERIES_UPDATED.equals(msgType) + || DataConstants.ALARM.equals(msgType); + } + + protected boolean isSupportedOriginator(EntityType entityType) { + switch (entityType) { + case DEVICE: + case ASSET: + case ENTITY_VIEW: + case DASHBOARD: + case TENANT: + case CUSTOMER: + case EDGE: + return true; + default: + return false; + } + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoIntegrationTest.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java similarity index 51% rename from application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoIntegrationTest.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java index 9252e3c4c4..84ad05f178 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/sql/MqttServerSideRpcProtoIntegrationTest.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/BaseTbMsgPushNodeConfiguration.java @@ -13,12 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.mqtt.rpc.sql; +package org.thingsboard.rule.engine.edge; -import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.transport.mqtt.rpc.AbstractMqttServerSideRpcProtoIntegrationTest; +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.DataConstants; +@Data +public class BaseTbMsgPushNodeConfiguration implements NodeConfiguration { -@DaoSqlTest -public class MqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcProtoIntegrationTest { + private String scope; + + @Override + public BaseTbMsgPushNodeConfiguration defaultConfiguration() { + BaseTbMsgPushNodeConfiguration configuration = new BaseTbMsgPushNodeConfiguration(); + configuration.setScope(DataConstants.SERVER_SCOPE); + return configuration; + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java index 013f9a3fc4..c7bf3bed0e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNode.java @@ -15,17 +15,19 @@ */ package org.thingsboard.rule.engine.edge; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.msg.TbMsg; +import java.util.UUID; + @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -57,22 +59,37 @@ import org.thingsboard.server.common.msg.TbMsg; icon = "cloud_upload", ruleChainTypes = RuleChainType.EDGE ) -public class TbMsgPushToCloudNode implements TbNode { +public class TbMsgPushToCloudNode extends AbstractTbMsgPushNode { + + // Implementation of this node is done on the Edge + + @Override + Object buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId, Object eventType, JsonNode entityBody) { + return null; + } - private TbMsgPushToCloudNodeConfiguration config; + @Override + Object getEventTypeByEntityType(EntityType entityType) { + return null; + } + + @Override + Object getAlarmEventType() { + return null; + } @Override - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { - this.config = TbNodeUtils.convert(configuration, TbMsgPushToCloudNodeConfiguration.class); + String getIgnoredMessageSource() { + return null; } @Override - public void onMsg(TbContext ctx, TbMsg msg) { - // Implementation of this node is done on the Edge + protected Class getConfigClazz() { + return TbMsgPushToCloudNodeConfiguration.class; } @Override - public void destroy() { + void processMsg(TbContext ctx, TbMsg msg) { } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java index 6dcbf23a32..2c9e76fe61 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToCloudNodeConfiguration.java @@ -16,13 +16,12 @@ package org.thingsboard.rule.engine.edge; import lombok.Data; -import org.thingsboard.rule.engine.api.NodeConfiguration; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.DataConstants; +@EqualsAndHashCode(callSuper = true) @Data -public class TbMsgPushToCloudNodeConfiguration implements NodeConfiguration { - - private String scope; +public class TbMsgPushToCloudNodeConfiguration extends BaseTbMsgPushNodeConfiguration { @Override public TbMsgPushToCloudNodeConfiguration defaultConfiguration() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java index f8bd44bb61..3602a53ca4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNode.java @@ -15,23 +15,17 @@ */ package org.thingsboard.rule.engine.edge; -import com.fasterxml.jackson.core.type.TypeReference; 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.thingsboard.common.util.JacksonUtil; +import org.checkerframework.checker.nullness.qual.Nullable; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNode; -import org.thingsboard.rule.engine.api.TbNodeConfiguration; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; @@ -42,16 +36,11 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.session.SessionMsgType; -import javax.annotation.Nullable; -import java.util.HashMap; +import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.UUID; -import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; - @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -84,173 +73,108 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; icon = "cloud_download", ruleChainTypes = RuleChainType.CORE ) -public class TbMsgPushToEdgeNode implements TbNode { - - private TbMsgPushToEdgeNodeConfiguration config; +public class TbMsgPushToEdgeNode extends AbstractTbMsgPushNode { - private static final String SCOPE = "scope"; - - private static final int DEFAULT_PAGE_SIZE = 1000; + static final int DEFAULT_PAGE_SIZE = 100; @Override - public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { - this.config = TbNodeUtils.convert(configuration, TbMsgPushToEdgeNodeConfiguration.class); - } - - @Override - public void onMsg(TbContext ctx, TbMsg msg) { - if (DataConstants.EDGE_MSG_SOURCE.equalsIgnoreCase(msg.getMetaData().getValue(DataConstants.MSG_SOURCE_KEY))) { - log.debug("Ignoring msg from the cloud, msg [{}]", msg); - ctx.ack(msg); - return; - } - if (isSupportedOriginator(msg.getOriginator().getEntityType())) { - if (isSupportedMsgType(msg.getType())) { - processMsg(ctx, msg); - } else { - log.debug("Unsupported msg type {}", msg.getType()); - ctx.tellFailure(msg, new RuntimeException("Unsupported msg type '" + msg.getType() + "'")); - } - } else { - log.debug("Unsupported originator type {}", msg.getOriginator().getEntityType()); - ctx.tellFailure(msg, new RuntimeException("Unsupported originator type '" + msg.getOriginator().getEntityType() + "'")); - } - } - - private void processMsg(TbContext ctx, TbMsg msg) { - if (EntityType.EDGE.equals(msg.getOriginator().getEntityType())) { - EdgeEvent edgeEvent = buildEdgeEvent(msg, ctx); - if (edgeEvent != null) { - EdgeId edgeId = new EdgeId(msg.getOriginator().getId()); - notifyEdge(ctx, msg, edgeEvent, edgeId); - } - } else { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = ctx.getEdgeService().findRelatedEdgeIdsByEntityId(ctx.getTenantId(), msg.getOriginator(), pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId edgeId : pageData.getData()) { - EdgeEvent edgeEvent = buildEdgeEvent(msg, ctx); - if (edgeEvent == null) { - log.debug("Edge event type is null. Entity Type {}", msg.getOriginator().getEntityType()); - ctx.tellFailure(msg, new RuntimeException("Edge event type is null. Entity Type '" + msg.getOriginator().getEntityType() + "'")); - } else { - notifyEdge(ctx, msg, edgeEvent, edgeId); - } - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - } - } - - private void notifyEdge(TbContext ctx, TbMsg msg, EdgeEvent edgeEvent, EdgeId edgeId) { - edgeEvent.setEdgeId(edgeId); - ctx.getEdgeEventService().save(edgeEvent); - ctx.tellNext(msg, SUCCESS); - ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId); - } - - private EdgeEvent buildEdgeEvent(TbMsg msg, TbContext ctx) { - String msgType = msg.getType(); - if (DataConstants.ALARM.equals(msgType)) { - return buildEdgeEvent(ctx.getTenantId(), EdgeEventActionType.ADDED, getUUIDFromMsgData(msg), EdgeEventType.ALARM, null); - } else { - EdgeEventType edgeEventTypeByEntityType = EdgeUtils.getEdgeEventTypeByEntityType(msg.getOriginator().getEntityType()); - if (edgeEventTypeByEntityType == null) { - return null; - } - EdgeEventActionType actionType = getEdgeEventActionTypeByMsgType(msgType); - Map entityBody = new HashMap<>(); - Map metadata = msg.getMetaData().getData(); - JsonNode dataJson = JacksonUtil.toJsonNode(msg.getData()); - switch (actionType) { - case ATTRIBUTES_UPDATED: - case POST_ATTRIBUTES: - entityBody.put("kv", dataJson); - 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, getScope(metadata)); - break; - case TIMESERIES_UPDATED: - entityBody.put("data", dataJson); - entityBody.put("ts", metadata.get("ts")); - break; - } - return buildEdgeEvent(ctx.getTenantId(), actionType, msg.getOriginator().getId(), edgeEventTypeByEntityType, JacksonUtil.valueToTree(entityBody)); - } - } - - private String getScope(Map metadata) { - String scope = metadata.get(SCOPE); - if (StringUtils.isEmpty(scope)) { - scope = config.getScope(); - } - return scope; - } - - private EdgeEvent buildEdgeEvent(TenantId tenantId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) { + EdgeEvent buildEvent(TenantId tenantId, EdgeEventActionType eventAction, UUID entityId, + EdgeEventType eventType, JsonNode entityBody) { EdgeEvent edgeEvent = new EdgeEvent(); edgeEvent.setTenantId(tenantId); - edgeEvent.setAction(edgeEventAction); + edgeEvent.setAction(eventAction); edgeEvent.setEntityId(entityId); - edgeEvent.setType(edgeEventType); + edgeEvent.setType(eventType); edgeEvent.setBody(entityBody); return edgeEvent; } - private UUID getUUIDFromMsgData(TbMsg msg) { - JsonNode data = JacksonUtil.toJsonNode(msg.getData()).get("id"); - String id = JacksonUtil.convertValue(data.get("id"), String.class); - return UUID.fromString(id); + @Override + EdgeEventType getEventTypeByEntityType(EntityType entityType) { + return EdgeUtils.getEdgeEventTypeByEntityType(entityType); } - private EdgeEventActionType getEdgeEventActionTypeByMsgType(String msgType) { - EdgeEventActionType actionType; - if (SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType)) { - actionType = EdgeEventActionType.TIMESERIES_UPDATED; - } else if (DataConstants.ATTRIBUTES_UPDATED.equals(msgType)) { - actionType = EdgeEventActionType.ATTRIBUTES_UPDATED; - } else if (SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType)) { - actionType = EdgeEventActionType.POST_ATTRIBUTES; - } else { - actionType = EdgeEventActionType.ATTRIBUTES_DELETED; - } - return actionType; + @Override + EdgeEventType getAlarmEventType() { + return EdgeEventType.ALARM; } - private boolean isSupportedOriginator(EntityType entityType) { - switch (entityType) { - case DEVICE: - case ASSET: - case ENTITY_VIEW: - case DASHBOARD: - case TENANT: - case CUSTOMER: - case EDGE: - return true; - default: - return false; - } + @Override + String getIgnoredMessageSource() { + return DataConstants.EDGE_MSG_SOURCE; } - private boolean isSupportedMsgType(String msgType) { - return SessionMsgType.POST_TELEMETRY_REQUEST.name().equals(msgType) - || SessionMsgType.POST_ATTRIBUTES_REQUEST.name().equals(msgType) - || DataConstants.ATTRIBUTES_UPDATED.equals(msgType) - || DataConstants.ATTRIBUTES_DELETED.equals(msgType) - || DataConstants.ALARM.equals(msgType); + @Override + protected Class getConfigClazz() { + return TbMsgPushToEdgeNodeConfiguration.class; } @Override - public void destroy() { + protected void processMsg(TbContext ctx, TbMsg msg) { + try { + if (EntityType.EDGE.equals(msg.getOriginator().getEntityType())) { + EdgeEvent edgeEvent = buildEvent(msg, ctx); + EdgeId edgeId = new EdgeId(msg.getOriginator().getId()); + ListenableFuture future = notifyEdge(ctx, edgeEvent, edgeId); + FutureCallback futureCallback = new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void result) { + ctx.tellSuccess(msg); + } + + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }; + Futures.addCallback(future, futureCallback, ctx.getDbCallbackExecutor()); + } else { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = ctx.getEdgeService().findRelatedEdgeIdsByEntityId(ctx.getTenantId(), msg.getOriginator(), pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId edgeId : pageData.getData()) { + EdgeEvent edgeEvent = buildEvent(msg, ctx); + futures.add(notifyEdge(ctx, edgeEvent, edgeId)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + + if (futures.isEmpty()) { + // ack in case no edges are related to provided entity + ctx.ack(msg); + } else { + Futures.addCallback(Futures.allAsList(futures), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable List voids) { + ctx.tellSuccess(msg); + } + + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }, ctx.getDbCallbackExecutor()); + } + } + } catch (Exception e) { + log.error("Failed to build edge event", e); + ctx.tellFailure(msg, e); + } + } + + private ListenableFuture notifyEdge(TbContext ctx, EdgeEvent edgeEvent, EdgeId edgeId) { + edgeEvent.setEdgeId(edgeId); + ListenableFuture future = ctx.getEdgeEventService().saveAsync(edgeEvent); + return Futures.transform(future, result -> { + ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId); + return null; + }, ctx.getDbCallbackExecutor()); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java index 05015e1a48..6050fc0e90 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeConfiguration.java @@ -16,13 +16,12 @@ package org.thingsboard.rule.engine.edge; import lombok.Data; -import org.thingsboard.rule.engine.api.NodeConfiguration; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.DataConstants; +@EqualsAndHashCode(callSuper = true) @Data -public class TbMsgPushToEdgeNodeConfiguration implements NodeConfiguration { - - private String scope; +public class TbMsgPushToEdgeNodeConfiguration extends BaseTbMsgPushNodeConfiguration { @Override public TbMsgPushToEdgeNodeConfiguration defaultConfiguration() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index 5acf944c98..836497cced 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -158,7 +158,7 @@ public class TbKafkaNode implements TbNode { } private void processRecord(TbContext ctx, TbMsg msg, RecordMetadata metadata, Exception e) { - if (metadata != null) { + if (e == null) { TbMsg next = processResponse(ctx, msg, metadata); ctx.tellNext(next, TbRelationTypes.SUCCESS); } else { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index 837b5272ec..9b9974cbb9 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -99,7 +99,7 @@ public class TbDeviceProfileNode implements TbNode { log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount); } if (!config.isPersistAlarmRulesState() && ctx.isLocalEntity(ctx.getSelfId())) { - log.info("[{}] Going to cleanup rule node states", ctx.getSelfId()); + log.debug("[{}] Going to cleanup rule node states", ctx.getSelfId()); ctx.clearRuleNodeStates(); } } diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 8b4dbad1d8..11b35b2a24 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1 +1 @@ -System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/material/form-field","@angular/material/checkbox","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/input","@angular/common","@angular/platform-browser","@angular/material/select","@angular/material/core","@angular/material/expansion","@shared/components/button/toggle-password.component","@shared/components/file-input.component","@shared/components/queue/queue-type-list.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/cdk/keycodes","@angular/material/chips","@angular/material/icon","@angular/flex-layout/extended","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/material/tooltip","rxjs/operators","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@home/components/public-api","@shared/components/relation/relation-type-autocomplete.component","@shared/components/entity/entity-subtype-list.component","@home/components/relation/relation-filters.component","rxjs","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component"],(function(e){"use strict";var t,o,r,a,n,l,i,s,m,u,p,d,f,c,g,x,y,b,h,C,F,L,v,I,N,T,q,k,M,S,A,G,D,V,E,P,R,w,O,H,U,K,B,j,z,_,Q,$,W,Y,J,Z,X,ee,te,oe,re,ae,ne,le,ie,se,me,ue,pe,de,fe,ce,ge,xe,ye,be,he,Ce,Fe,Le,ve,Ie;return{setters:[function(e){t=e,o=e.Component,r=e.Pipe,a=e.ViewChild,n=e.forwardRef,l=e.Input,i=e.NgModule},function(e){s=e.RuleNodeConfigurationComponent,m=e.AttributeScope,u=e.telemetryTypeTranslations,p=e.ServiceType,d=e.AlarmSeverity,f=e.alarmSeverityTranslations,c=e.EntitySearchDirection,g=e.entitySearchDirectionTranslations,x=e.EntityType,y=e.PageComponent,b=e.MessageType,h=e.messageTypeNames,C=e,F=e.SharedModule,L=e.AggregationType,v=e.aggregationTranslations,I=e.alarmStatusTranslations,N=e.AlarmStatus},function(e){T=e},function(e){q=e,k=e.Validators,M=e.NgControl,S=e.NG_VALUE_ACCESSOR,A=e.NG_VALIDATORS,G=e.FormControl},function(e){D=e},function(e){V=e},function(e){E=e},function(e){P=e},function(e){R=e},function(e){w=e,O=e.CommonModule},function(e){H=e},function(e){U=e},function(e){K=e},function(e){B=e},function(e){j=e},function(e){z=e},function(e){_=e},function(e){Q=e,$=e.isDefinedAndNotNull,W=e.isNotEmptyStr},function(e){Y=e},function(e){J=e},function(e){Z=e.ENTER,X=e.COMMA,ee=e.SEMICOLON},function(e){te=e},function(e){oe=e},function(e){re=e},function(e){ae=e},function(e){ne=e},function(e){le=e.coerceBooleanProperty},function(e){ie=e},function(e){se=e},function(e){me=e.distinctUntilChanged,ue=e.startWith,pe=e.map,de=e.mergeMap,fe=e.share},function(e){ce=e},function(e){ge=e},function(e){xe=e.HomeComponentsModule},function(e){ye=e},function(e){be=e},function(e){he=e},function(e){Ce=e.of},function(e){Fe=e},function(e){Le=e},function(e){ve=e},function(e){Ie=e}],execute:function(){class Ne extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",Ne),Ne.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ne,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ne.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ne,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"
",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ne,decorators:[{type:o,args:[{selector:"tb-node-empty-config",template:"
",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Te{constructor(e){this.sanitizer=e}transform(e){return this.sanitizer.bypassSecurityTrustHtml(e)}}e("SafeHtmlPipe",Te),Te.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Te,deps:[{token:H.DomSanitizer}],target:t.ɵɵFactoryTarget.Pipe}),Te.ɵpipe=t.ɵɵngDeclarePipe({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Te,name:"safeHtml"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Te,decorators:[{type:r,args:[{name:"safeHtml"}]}],ctorParameters:function(){return[{type:H.DomSanitizer}]}});class qe extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[k.required,k.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[k.required,k.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",qe),qe.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qe,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qe.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:qe,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qe,decorators:[{type:o,args:[{selector:"tb-action-node-assign-to-customer-config",templateUrl:"./assign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ke extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[k.required]],notifyDevice:[!e||e.notifyDevice,[]]})}}var Me;e("AttributesConfigComponent",ke),ke.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ke,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ke.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ke,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ke,decorators:[{type:o,args:[{selector:"tb-action-node-attributes-config",templateUrl:"./attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(Me||(Me={}));const Se=new Map([[Me.CUSTOMER,"tb.rulenode.originator-customer"],[Me.TENANT,"tb.rulenode.originator-tenant"],[Me.RELATED,"tb.rulenode.originator-related"],[Me.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);var Ae;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(Ae||(Ae={}));const Ge=new Map([[Ae.CIRCLE,"tb.rulenode.perimeter-circle"],[Ae.POLYGON,"tb.rulenode.perimeter-polygon"]]);var De;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(De||(De={}));const Ve=new Map([[De.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[De.SECONDS,"tb.rulenode.time-unit-seconds"],[De.MINUTES,"tb.rulenode.time-unit-minutes"],[De.HOURS,"tb.rulenode.time-unit-hours"],[De.DAYS,"tb.rulenode.time-unit-days"]]);var Ee;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(Ee||(Ee={}));const Pe=new Map([[Ee.METER,"tb.rulenode.range-unit-meter"],[Ee.KILOMETER,"tb.rulenode.range-unit-kilometer"],[Ee.FOOT,"tb.rulenode.range-unit-foot"],[Ee.MILE,"tb.rulenode.range-unit-mile"],[Ee.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var Re;!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(Re||(Re={}));const we=new Map([[Re.TITLE,"tb.rulenode.entity-details-title"],[Re.COUNTRY,"tb.rulenode.entity-details-country"],[Re.STATE,"tb.rulenode.entity-details-state"],[Re.CITY,"tb.rulenode.entity-details-city"],[Re.ZIP,"tb.rulenode.entity-details-zip"],[Re.ADDRESS,"tb.rulenode.entity-details-address"],[Re.ADDRESS2,"tb.rulenode.entity-details-address2"],[Re.PHONE,"tb.rulenode.entity-details-phone"],[Re.EMAIL,"tb.rulenode.entity-details-email"],[Re.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Oe,He,Ue;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(Oe||(Oe={})),function(e){e.ASC="ASC",e.DESC="DESC"}(He||(He={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Ue||(Ue={}));const Ke=new Map([[Ue.STANDARD,"tb.rulenode.sqs-queue-standard"],[Ue.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Be=["anonymous","basic","cert.PEM"],je=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),ze=["sas","cert.PEM"],_e=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var Qe;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Qe||(Qe={}));const $e=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],We=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);class Ye extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=ze,this.azureIotHubCredentialsTypeTranslationsMap=_e}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[k.required]],host:[e?e.host:null,[k.required]],port:[e?e.port:null,[k.required,k.min(1),k.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[k.required,k.min(1),k.max(200)]],clientId:[e?e.clientId:null,[k.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[k.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),o=t.get("type").value;switch(e&&t.reset({type:o},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),o){case"sas":t.get("sasKey").setValidators([k.required]);break;case"cert.PEM":t.get("privateKey").setValidators([k.required]),t.get("privateKeyFileName").setValidators([k.required]),t.get("cert").setValidators([k.required]),t.get("certFileName").setValidators([k.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}}e("AzureIotHubConfigComponent",Ye),Ye.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ye,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ye.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ye,selector:"tb-action-node-azure-iot-hub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:B.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:B.MatAccordion,selector:"mat-accordion",inputs:["multi","displayMode","togglePosition","hideToggle"],exportAs:["matAccordion"]},{type:B.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:B.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:q.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:w.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ye,decorators:[{type:o,args:[{selector:"tb-action-node-azure-iot-hub-config",templateUrl:"./azure-iot-hub-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Je extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.serviceType=p.TB_RULE_ENGINE}configForm(){return this.checkPointConfigForm}onConfigurationSet(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[k.required]]})}}e("CheckPointConfigComponent",Je),Je.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Je,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Je.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Je,selector:"tb-action-node-check-point-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',components:[{type:_.QueueTypeListComponent,selector:"tb-queue-type-list",inputs:["required","disabled","queueType"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Je,decorators:[{type:o,args:[{selector:"tb-action-node-check-point-config",templateUrl:"./check-point-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ze extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[k.required]],alarmType:[e?e.alarmType:null,[k.required]]})}testScript(){const e=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(e,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/clear_alarm_node_script_fn").subscribe((e=>{e&&this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",Ze),Ze.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ze,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Ze.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ze,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ze,decorators:[{type:o,args:[{selector:"tb-action-node-clear-alarm-config",templateUrl:"./clear-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class Xe extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r,this.alarmSeverities=Object.keys(d),this.alarmSeverityTranslationMap=f,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[k.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData"]}updateValidators(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([k.required]),this.createAlarmConfigForm.get("severity").setValidators([k.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})}testScript(){const e=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(e,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/create_alarm_node_script_fn").subscribe((e=>{e&&this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(e)}))}removeKey(e,t){const o=this.createAlarmConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.createAlarmConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}onValidate(){this.createAlarmConfigForm.get("useMessageAlarmData").value||this.jsFuncComponent.validateOnSubmit()}}e("CreateAlarmConfigComponent",Xe),Xe.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xe,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Xe.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Xe,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:re.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xe,decorators:[{type:o,args:[{selector:"tb-action-node-create-alarm-config",templateUrl:"./create-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class et extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[k.required]],entityType:[e?e.entityType:null,[k.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[k.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[k.required,k.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([k.required,k.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==x.DEVICE&&t!==x.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([k.required,k.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}}e("CreateRelationConfigComponent",et),et.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:et,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),et.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:et,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","showLabel","required","disabled"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:et,decorators:[{type:o,args:[{selector:"tb-action-node-create-relation-config",templateUrl:"./create-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class tt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[k.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[k.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[k.required,k.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,o=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([k.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&o?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([k.required,k.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}}e("DeleteRelationConfigComponent",tt),tt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:tt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),tt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:tt,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","showLabel","required","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:tt,decorators:[{type:o,args:[{selector:"tb-action-node-delete-relation-config",templateUrl:"./delete-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ot extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,k.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,k.required]})}}e("DeviceProfileConfigComponent",ot),ot.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ot,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ot.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ot,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ot,decorators:[{type:o,args:[{selector:"tb-device-profile-config",templateUrl:"./device-profile-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class rt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[k.required,k.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[k.required,k.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[k.required]]})}prepareInputConfig(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(){const e=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,"rulenode/generator_node_script_fn").subscribe((e=>{e&&this.generatorConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("GeneratorConfigComponent",rt),rt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:rt,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),rt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:rt,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:ne.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:rt,decorators:[{type:o,args:[{selector:"tb-action-node-generator-config",templateUrl:"./generator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class at extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Ae,this.perimeterTypes=Object.keys(Ae),this.perimeterTypeTranslationMap=Ge,this.rangeUnits=Object.keys(Ee),this.rangeUnitTranslationMap=Pe,this.timeUnits=Object.keys(De),this.timeUnitsTranslationMap=Ve}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[k.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[k.required]],perimeterType:[e?e.perimeterType:null,[k.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[k.required,k.min(1),k.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[k.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[k.required,k.min(1),k.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[k.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([k.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||o!==Ae.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([k.required,k.min(-90),k.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([k.required,k.min(-180),k.max(180)]),this.geoActionConfigForm.get("range").setValidators([k.required,k.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([k.required])),t||o!==Ae.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([k.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoActionConfigComponent",at),at.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:at,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),at.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:at,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:at,decorators:[{type:o,args:[{selector:"tb-action-node-gps-geofencing-config",templateUrl:"./gps-geo-action-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class nt extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.injector=o,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.ngControl=this.injector.get(M),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const o of Object.keys(e))Object.prototype.hasOwnProperty.call(e,o)&&t.push(this.fb.group({key:[o,[k.required]],value:[e[o],[k.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[k.required]],value:["",[k.required]]}))}validate(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigComponent",nt),nt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:nt,deps:[{token:T.Store},{token:P.TranslateService},{token:t.Injector},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),nt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:nt,selector:"tb-kv-map-config",inputs:{disabled:"disabled",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:S,useExisting:n((()=>nt)),multi:!0},{provide:A,useExisting:n((()=>nt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#0000008a;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0;align-self:baseline}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:re.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{type:q.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:se.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:nt,decorators:[{type:o,args:[{selector:"tb-kv-map-config",templateUrl:"./kv-map-config.component.html",styleUrls:["./kv-map-config.component.scss"],providers:[{provide:S,useExisting:n((()=>nt)),multi:!0},{provide:A,useExisting:n((()=>nt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:t.Injector},{type:q.FormBuilder}]},propDecorators:{disabled:[{type:l}],requiredText:[{type:l}],keyText:[{type:l}],keyRequiredText:[{type:l}],valText:[{type:l}],valRequiredText:[{type:l}],hintText:[{type:l}],required:[{type:l}]}});class lt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=$e,this.ToByteStandartCharsetTypeTranslationMap=We}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[k.required]],bootstrapServers:[e?e.bootstrapServers:null,[k.required]],retries:[e?e.retries:null,[k.min(0)]],batchSize:[e?e.batchSize:null,[k.min(0)]],linger:[e?e.linger:null,[k.min(0)]],bufferMemory:[e?e.bufferMemory:null,[k.min(0)]],acks:[e?e.acks:null,[k.required]],keySerializer:[e?e.keySerializer:null,[k.required]],valueSerializer:[e?e.valueSerializer:null,[k.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([k.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",lt),lt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:lt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),lt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:lt,selector:"tb-action-node-kafka-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:lt,decorators:[{type:o,args:[{selector:"tb-action-node-kafka-config",templateUrl:"./kafka-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class it extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/log_node_script_fn").subscribe((e=>{e&&this.logConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",it),it.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:it,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),it.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:it,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:it,decorators:[{type:o,args:[{selector:"tb-action-node-log-config",templateUrl:"./log-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class st extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRquired=!0,this.allCredentialsTypes=Be,this.credentialsTypeTranslationsMap=je,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[k.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(me()).subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const o=e[t];if(!o.firstChange&&o.currentValue!==o.previousValue&&o.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){$(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))}setDisabledState(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([k.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRquired?[k.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(k.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return o=>{t||(t=[Object.keys(o.controls)]);return(null==o?void 0:o.controls)&&t.some((t=>t.every((t=>!e(o.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}}e("CredentialsConfigComponent",st),st.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:st,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),st.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:st,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRquired:"passwordFieldRquired"},providers:[{provide:S,useExisting:n((()=>st)),multi:!0},{provide:A,useExisting:n((()=>st)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:t,template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',components:[{type:B.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:B.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:B.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:B.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:B.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{type:D.MatLabel,selector:"mat-label"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:w.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:w.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:st,decorators:[{type:o,args:[{selector:"tb-credentials-config",templateUrl:"./credentials-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>st)),multi:!0},{provide:A,useExisting:n((()=>st)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]},propDecorators:{required:[{type:l}],disableCertPemCredentials:[{type:l}],passwordFieldRquired:[{type:l}]}});class mt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[]}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[k.required]],host:[e?e.host:null,[k.required]],port:[e?e.port:null,[k.required,k.min(1),k.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[k.required,k.min(1),k.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&W(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]}),this.subscriptions.push(this.mqttConfigForm.get("clientId").valueChanges.subscribe((e=>{W(e)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1})})))}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}}e("MqttConfigComponent",mt),mt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:mt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:mt,selector:"tb-action-node-mqtt-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n
tb.rulenode.client-id-hint
\n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:st,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:mt,decorators:[{type:o,args:[{selector:"tb-action-node-mqtt-config",templateUrl:"./mqtt-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ut extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[k.required,k.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[k.required]]})}}e("MsgCountConfigComponent",ut),ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ut,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ut,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ut,decorators:[{type:o,args:[{selector:"tb-action-node-msg-count-config",templateUrl:"./msg-count-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class pt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[k.required,k.min(1),k.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([k.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([k.required,k.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",pt),pt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:pt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),pt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:pt,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:pt,decorators:[{type:o,args:[{selector:"tb-action-node-msg-delay-config",templateUrl:"./msg-delay-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class dt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[k.required]],topicName:[e?e.topicName:null,[k.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[k.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[k.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",dt),dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:dt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:dt,selector:"tb-action-node-pub-sub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:dt,decorators:[{type:o,args:[{selector:"tb-action-node-pub-sub-config",templateUrl:"./pubsub-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ft extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[k.required]]})}}e("PushToCloudConfigComponent",ft),ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ft,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ft,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ft,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-cloud-config",templateUrl:"./push-to-cloud-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ct extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[k.required]]})}}e("PushToEdgeConfigComponent",ct),ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ct,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ct,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ct,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-edge-config",templateUrl:"./push-to-edge-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class gt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[k.required]],port:[e?e.port:null,[k.required,k.min(1),k.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[k.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[k.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",gt),gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:gt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:gt,selector:"tb-action-node-rabbit-mq-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:gt,decorators:[{type:o,args:[{selector:"tb-action-node-rabbit-mq-config",templateUrl:"./rabbit-mq-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class xt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys(Qe)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[k.required]],requestMethod:[e?e.requestMethod:null,[k.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[k.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,o=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[k.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[k.required,k.min(1),k.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([k.min(0)])),o?this.restApiCallConfigForm.get("maxQueueSize").setValidators([k.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}}e("RestApiCallConfigComponent",xt),xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:xt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:xt,selector:"tb-action-node-rest-api-call-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:st,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:xt,decorators:[{type:o,args:[{selector:"tb-action-node-rest-api-call-config",templateUrl:"./rest-api-call-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class yt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}}e("RpcReplyConfigComponent",yt),yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:yt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:yt,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:yt,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-reply-config",templateUrl:"./rpc-reply-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class bt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[k.required,k.min(0)]]})}}e("RpcRequestConfigComponent",bt),bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:bt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:bt,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:bt,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-request-config",templateUrl:"./rpc-request-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ht extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[k.required,k.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[k.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",ht),ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ht,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ht,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ht,decorators:[{type:o,args:[{selector:"tb-action-node-custom-table-config",templateUrl:"./save-to-custom-table-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ct extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,o=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([k.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([k.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([k.required,k.min(1),k.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([k.required,k.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(o?[k.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(o?[k.required,k.min(1),k.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}}e("SendEmailConfigComponent",Ct),Ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ct,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ct,selector:"tb-action-node-send-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ce.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ct,decorators:[{type:o,args:[{selector:"tb-action-node-send-email-config",templateUrl:"./send-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ft extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[k.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[k.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([k.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",Ft),Ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ft,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ft,selector:"tb-action-node-send-sms-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:ge.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ft,decorators:[{type:o,args:[{selector:"tb-action-node-send-sms-config",templateUrl:"./send-sms-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Lt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[k.required]],accessKeyId:[e?e.accessKeyId:null,[k.required]],secretAccessKey:[e?e.secretAccessKey:null,[k.required]],region:[e?e.region:null,[k.required]]})}}e("SnsConfigComponent",Lt),Lt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Lt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Lt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Lt,selector:"tb-action-node-sns-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Lt,decorators:[{type:o,args:[{selector:"tb-action-node-sns-config",templateUrl:"./sns-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class vt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Ue,this.sqsQueueTypes=Object.keys(Ue),this.sqsQueueTypeTranslationsMap=Ke}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[k.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[k.required]],delaySeconds:[e?e.delaySeconds:null,[k.min(0),k.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[k.required]],secretAccessKey:[e?e.secretAccessKey:null,[k.required]],region:[e?e.region:null,[k.required]]})}}e("SqsConfigComponent",vt),vt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:vt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),vt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:vt,selector:"tb-action-node-sqs-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:vt,decorators:[{type:o,args:[{selector:"tb-action-node-sqs-config",templateUrl:"./sqs-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class It extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[k.required,k.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",It),It.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:It,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),It.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:It,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:It,decorators:[{type:o,args:[{selector:"tb-action-node-timeseries-config",templateUrl:"./timeseries-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Nt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[k.required,k.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[k.required,k.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",Nt),Nt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Nt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Nt,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Nt,decorators:[{type:o,args:[{selector:"tb-action-node-un-assign-to-customer-config",templateUrl:"./unassign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Tt extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[k.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[k.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}}e("DeviceRelationsQueryConfigComponent",Tt),Tt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Tt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Tt,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:S,useExisting:n((()=>Tt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ye.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]},{type:be.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","disabled","entityType"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Tt,decorators:[{type:o,args:[{selector:"tb-device-relations-query-config",templateUrl:"./device-relations-query-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>Tt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class qt extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[k.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigComponent",qt),qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:qt,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:S,useExisting:n((()=>qt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:he.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qt,decorators:[{type:o,args:[{selector:"tb-relations-query-config",templateUrl:"./relations-query-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>qt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class kt extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.truncate=o,this.fb=r,this.placeholder="tb.rulenode.message-type",this.separatorKeysCodes=[Z,X,ee],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(b))this.messageTypesList.push({name:h.get(b[e]),value:e})}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(ue(""),pe((e=>e||"")),de((e=>this.fetchMessageTypes(e))),fe())}ngAfterViewInit(){}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return!!(e&&null!=e&&e.length>0)}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ce(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ce(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t=null;const o=e.trim(),r=this.messageTypesList.find((e=>e.name===o));t=r?{name:r.name,value:r.value}:{name:o,value:o},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}}e("MessageTypesConfigComponent",kt),kt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:kt,deps:[{token:T.Store},{token:P.TranslateService},{token:C.TruncatePipe},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),kt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:kt,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:S,useExisting:n((()=>kt)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Fe.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Fe.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:Fe.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,highlight:Le.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:kt,decorators:[{type:o,args:[{selector:"tb-message-types-config",templateUrl:"./message-types-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>kt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:C.TruncatePipe},{type:q.FormBuilder}]},propDecorators:{required:[{type:l}],label:[{type:l}],placeholder:[{type:l}],disabled:[{type:l}],chipList:[{type:a,args:["chipList",{static:!1}]}],matAutocomplete:[{type:a,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:a,args:["messageTypeInput",{static:!1}]}]}});class Mt{}e("RulenodeCoreConfigCommonModule",Mt),Mt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Mt.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,declarations:[nt,Tt,qt,kt,st,Te],imports:[O,F,xe],exports:[nt,Tt,qt,kt,st,Te]}),Mt.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,imports:[[O,F,xe]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,decorators:[{type:i,args:[{declarations:[nt,Tt,qt,kt,st,Te],imports:[O,F,xe],exports:[nt,Tt,qt,kt,st,Te]}]}]});class St{}e("RuleNodeCoreConfigActionModule",St),St.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,deps:[],target:t.ɵɵFactoryTarget.NgModule}),St.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,declarations:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft],imports:[O,F,xe,Mt],exports:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft]}),St.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,imports:[[O,F,xe,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,decorators:[{type:i,args:[{declarations:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft],imports:[O,F,xe,Mt],exports:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft]}]}]});class At extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[k.required]],outputValueKey:[e?e.outputValueKey:null,[k.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[k.min(0),k.max(15)]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([k.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",At),At.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:At,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),At.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:At,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:t,template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:At,decorators:[{type:o,args:[{selector:"tb-enrichment-node-calculate-delta-config",templateUrl:"./calculate-delta-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Gt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.customerAttributesConfigForm}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[k.required]]})}}e("CustomerAttributesConfigComponent",Gt),Gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Gt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Gt,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Gt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-customer-attributes-config",templateUrl:"./customer-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Dt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[k.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})}removeKey(e,t){const o=this.deviceAttributesConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.deviceAttributesConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.deviceAttributesConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.deviceAttributesConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("DeviceAttributesConfigComponent",Dt),Dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Dt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Dt,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:Tt,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Dt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-device-attributes-config",templateUrl:"./device-attributes-config.component.html",styleUrls:["./device-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Vt extends s{constructor(e,t,o){super(e),this.store=e,this.translate=t,this.fb=o,this.entityDetailsTranslationsMap=we,this.entityDetailsList=[],this.searchText="",this.displayDetailsFn=this.displayDetails.bind(this);for(const e of Object.keys(Re))this.entityDetailsList.push(Re[e]);this.detailsFormControl=new G(""),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(ue(""),pe((e=>e||"")),de((e=>this.fetchEntityDetails(e))),fe())}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[k.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})}displayDetails(e){return e?this.translate.instant(we.get(e)):void 0}fetchEntityDetails(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ce(this.entityDetailsList.filter((t=>this.translate.instant(we.get(Re[t])).toUpperCase().includes(e))))}return Ce(this.entityDetailsList)}detailsFieldSelected(e){this.addDetailsField(e.option.value),this.clear("")}removeDetailsField(e){const t=this.entityDetailsConfigForm.get("detailsList").value;if(t){const o=t.indexOf(e);o>=0&&(t.splice(o,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}}addDetailsField(e){let t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))}onEntityDetailsInputFocus(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}}e("EntityDetailsConfigComponent",Vt),Vt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Vt,deps:[{token:T.Store},{token:P.TranslateService},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Vt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Vt,selector:"tb-enrichment-node-entity-details-config",viewQueries:[{propertyName:"detailsInput",first:!0,predicate:["detailsInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Fe.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Fe.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:Fe.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,highlight:Le.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Vt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-entity-details-config",templateUrl:"./entity-details-config.component.html",styleUrls:["./entity-details-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:q.FormBuilder}]},propDecorators:{detailsInput:[{type:a,args:["detailsInput",{static:!1}]}]}});class Et extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee],this.aggregationTypes=L,this.aggregations=Object.keys(L),this.aggregationTypesTranslations=v,this.fetchMode=Oe,this.fetchModes=Object.keys(Oe),this.samplingOrders=Object.keys(He),this.timeUnits=Object.values(De),this.timeUnitsTranslationMap=Ve}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],aggregation:[e?e.aggregation:null,[k.required]],fetchMode:[e?e.fetchMode:null,[k.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,o=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===Oe.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([k.required,k.min(2),k.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),o?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([k.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([k.required,k.min(1),k.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([k.required,k.min(1),k.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const o=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("GetTelemetryFromDatabaseConfigComponent",Et),Et.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Et,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Et.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Et,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n aggregation.function\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Et,decorators:[{type:o,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",templateUrl:"./get-telemetry-from-database-config.component.html",styleUrls:["./get-telemetry-from-database-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Pt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})}removeKey(e,t){const o=this.originatorAttributesConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.originatorAttributesConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.originatorAttributesConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.originatorAttributesConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("OriginatorAttributesConfigComponent",Pt),Pt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Pt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Pt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Pt,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Pt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-attributes-config",templateUrl:"./originator-attributes-config.component.html",styleUrls:["./originator-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Rt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.originatorFieldsConfigForm}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[k.required]]})}}e("OriginatorFieldsConfigComponent",Rt),Rt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Rt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Rt,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n',components:[{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Rt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-fields-config",templateUrl:"./originator-fields-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class wt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.relatedAttributesConfigForm}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[k.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[k.required]]})}}e("RelatedAttributesConfigComponent",wt),wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:wt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:wt,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:qt,selector:"tb-relations-query-config",inputs:["disabled","required"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:wt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-related-attributes-config",templateUrl:"./related-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ot extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.tenantAttributesConfigForm}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[k.required]]})}}e("TenantAttributesConfigComponent",Ot),Ot.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ot,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ot.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ot,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ot,decorators:[{type:o,args:[{selector:"tb-enrichment-node-tenant-attributes-config",templateUrl:"./tenant-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ht{}e("RulenodeCoreConfigEnrichmentModule",Ht),Ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Ht.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,declarations:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At],imports:[O,F,Mt],exports:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At]}),Ht.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,decorators:[{type:i,args:[{declarations:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At],imports:[O,F,Mt],exports:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At]}]}]});class Ut extends s{constructor(e,t,o){super(e),this.store=e,this.translate=t,this.fb=o,this.alarmStatusTranslationsMap=I,this.alarmStatusList=[],this.searchText="",this.displayStatusFn=this.displayStatus.bind(this);for(const e of Object.keys(N))this.alarmStatusList.push(N[e]);this.statusFormControl=new G(""),this.filteredAlarmStatus=this.statusFormControl.valueChanges.pipe(ue(""),pe((e=>e||"")),de((e=>this.fetchAlarmStatus(e))),fe())}ngOnInit(){super.ngOnInit()}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[k.required]]})}displayStatus(e){return e?this.translate.instant(I.get(e)):void 0}fetchAlarmStatus(e){const t=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ce(t.filter((t=>this.translate.instant(I.get(N[t])).toUpperCase().includes(e))))}return Ce(t)}alarmStatusSelected(e){this.addAlarmStatus(e.option.value),this.clear("")}removeAlarmStatus(e){const t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){const o=t.indexOf(e);o>=0&&(t.splice(o,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}}addAlarmStatus(e){let t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}getAlarmStatusList(){return this.alarmStatusList.filter((e=>-1===this.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(e)))}onAlarmStatusInputFocus(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.alarmStatusInput.nativeElement.blur(),this.alarmStatusInput.nativeElement.focus()}),0)}}e("CheckAlarmStatusComponent",Ut),Ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ut,deps:[{token:T.Store},{token:P.TranslateService},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ut,selector:"tb-filter-node-check-alarm-status-config",viewQueries:[{propertyName:"alarmStatusInput",first:!0,predicate:["alarmStatusInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Fe.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Fe.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:Fe.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,highlight:Le.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ut,decorators:[{type:o,args:[{selector:"tb-filter-node-check-alarm-status-config",templateUrl:"./check-alarm-status.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:q.FormBuilder}]},propDecorators:{alarmStatusInput:[{type:a,args:["alarmStatusInput",{static:!1}]}]}});class Kt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.checkMessageConfigForm}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})}validateConfig(){const e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0}removeMessageName(e){const t=this.checkMessageConfigForm.get("messageNames").value,o=t.indexOf(e);o>=0&&(t.splice(o,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))}removeMetadataName(e){const t=this.checkMessageConfigForm.get("metadataNames").value,o=t.indexOf(e);o>=0&&(t.splice(o,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))}addMessageName(e){const t=e.input;let o=e.value;if((o||"").trim()){o=o.trim();let e=this.checkMessageConfigForm.get("messageNames").value;e&&-1!==e.indexOf(o)||(e||(e=[]),e.push(o),this.checkMessageConfigForm.get("messageNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}addMetadataName(e){const t=e.input;let o=e.value;if((o||"").trim()){o=o.trim();let e=this.checkMessageConfigForm.get("metadataNames").value;e&&-1!==e.indexOf(o)||(e||(e=[]),e.push(o),this.checkMessageConfigForm.get("metadataNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("CheckMessageConfigComponent",Kt),Kt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Kt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Kt,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Kt,decorators:[{type:o,args:[{selector:"tb-filter-node-check-message-config",templateUrl:"./check-message-config.component.html",styleUrls:["./check-message-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Bt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.keys(c),this.entitySearchDirectionTranslationsMap=g}configForm(){return this.checkRelationConfigForm}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[k.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[k.required]:[]],relationType:[e?e.relationType:null,[k.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[k.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[k.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",Bt),Bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Bt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Bt,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","showLabel","required","disabled"]},{type:ve.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]},{type:ye.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Bt,decorators:[{type:o,args:[{selector:"tb-filter-node-check-relation-config",templateUrl:"./check-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class jt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Ae,this.perimeterTypes=Object.keys(Ae),this.perimeterTypeTranslationMap=Ge,this.rangeUnits=Object.keys(Ee),this.rangeUnitTranslationMap=Pe}configForm(){return this.geoFilterConfigForm}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[k.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[k.required]],perimeterType:[e?e.perimeterType:null,[k.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([k.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||o!==Ae.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([k.required,k.min(-90),k.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([k.required,k.min(-180),k.max(180)]),this.geoFilterConfigForm.get("range").setValidators([k.required,k.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([k.required])),t||o!==Ae.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([k.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoFilterConfigComponent",jt),jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:jt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:jt,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:jt,decorators:[{type:o,args:[{selector:"tb-filter-node-gps-geofencing-config",templateUrl:"./gps-geo-filter-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class zt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[k.required]]})}}e("MessageTypeConfigComponent",zt),zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:zt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:zt,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n
\n',components:[{type:kt,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:zt,decorators:[{type:o,args:[{selector:"tb-filter-node-message-type-config",templateUrl:"./message-type-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class _t extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[x.DEVICE,x.ASSET,x.ENTITY_VIEW,x.TENANT,x.CUSTOMER,x.USER,x.DASHBOARD,x.RULE_CHAIN,x.RULE_NODE]}configForm(){return this.originatorTypeConfigForm}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[k.required]]})}}e("OriginatorTypeConfigComponent",_t),_t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:_t,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),_t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:_t,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}\n"],components:[{type:Ie.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","disabled","allowedEntityTypes","ignoreAuthorityFilter"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:_t,decorators:[{type:o,args:[{selector:"tb-filter-node-originator-type-config",templateUrl:"./originator-type-config.component.html",styleUrls:["./originator-type-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Qt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/filter_node_script_fn").subscribe((e=>{e&&this.scriptConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Qt),Qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Qt,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Qt,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Qt,decorators:[{type:o,args:[{selector:"tb-filter-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class $t extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/switch_node_script_fn").subscribe((e=>{e&&this.switchConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",$t),$t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:$t,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),$t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:$t,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:$t,decorators:[{type:o,args:[{selector:"tb-filter-node-switch-config",templateUrl:"./switch-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class Wt{}e("RuleNodeCoreConfigFilterModule",Wt),Wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Wt.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,declarations:[Kt,Bt,jt,zt,_t,Qt,$t,Ut],imports:[O,F,Mt],exports:[Kt,Bt,jt,zt,_t,Qt,$t,Ut]}),Wt.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,decorators:[{type:i,args:[{declarations:[Kt,Bt,jt,zt,_t,Qt,$t,Ut],imports:[O,F,Mt],exports:[Kt,Bt,jt,zt,_t,Qt,$t,Ut]}]}]});class Yt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=Me,this.originatorSources=Object.keys(Me),this.originatorSourceTranslationMap=Se}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[k.required]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===Me.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([k.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})}}e("ChangeOriginatorConfigComponent",Yt),Yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Yt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Yt,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:qt,selector:"tb-relations-query-config",inputs:["disabled","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Yt,decorators:[{type:o,args:[{selector:"tb-transformation-node-change-originator-config",templateUrl:"./change-originator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Jt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/transformation_node_script_fn").subscribe((e=>{e&&this.scriptConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",Jt),Jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Jt,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Jt,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Jt,decorators:[{type:o,args:[{selector:"tb-transformation-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class Zt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",value:"false"},{name:"tb.mail-body-type.html",value:"true"},{name:"tb.mail-body-type.dynamic",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[k.required]],toTemplate:[e?e.toTemplate:null,[k.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[k.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null],bodyTemplate:[e?e.bodyTemplate:null,[k.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.pipe(ue([null==e?void 0:e.subjectTemplate])).subscribe((e=>{"dynamic"===e?(this.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").setValidators(k.required)):this.toEmailConfigForm.get("isHtmlTemplate").clearValidators(),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity()}))}}e("ToEmailConfigComponent",Zt),Zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Zt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Zt,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Zt,decorators:[{type:o,args:[{selector:"tb-transformation-node-to-email-config",templateUrl:"./to-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Xt{}e("RulenodeCoreConfigTransformModule",Xt),Xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Xt.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,declarations:[Yt,Jt,Zt],imports:[O,F,Mt],exports:[Yt,Jt,Zt]}),Xt.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,decorators:[{type:i,args:[{declarations:[Yt,Jt,Zt],imports:[O,F,Mt],exports:[Yt,Jt,Zt]}]}]});class eo extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=x}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[k.required]]})}}e("RuleChainInputComponent",eo),eo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:eo,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),eo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:eo,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',components:[{type:ve.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:eo,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-input-config",templateUrl:"./rule-chain-input.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class to extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",to),to.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:to,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),to.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:to,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n',directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:to,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-output-config",templateUrl:"./rule-chain-output.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class oo{}e("RuleNodeCoreConfigFlowModule",oo),oo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,deps:[],target:t.ɵɵFactoryTarget.NgModule}),oo.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,declarations:[eo,to],imports:[O,F,Mt],exports:[eo,to]}),oo.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,decorators:[{type:i,args:[{declarations:[eo,to],imports:[O,F,Mt],exports:[eo,to]}]}]});class ro{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","timeseries-key":"Timeseries key","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","client-id-hint":'Hint: Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Hint: Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-key-name":"Perimeter key name","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"alarm-severity-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body to substitute "Source" and "Target" key names'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"},"mail-body-type":{"plain-text":"Plain Text",html:"HTML",dynamic:"Dynamic"}}},!0)}(e)}}e("RuleNodeCoreConfigModule",ro),ro.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,deps:[{token:P.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),ro.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,declarations:[Ne],imports:[O,F],exports:[St,Wt,Ht,Xt,oo,Ne]}),ro.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,imports:[[O,F],St,Wt,Ht,Xt,oo]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,decorators:[{type:i,args:[{declarations:[Ne],imports:[O,F],exports:[St,Wt,Ht,Xt,oo,Ne]}]}],ctorParameters:function(){return[{type:P.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map +System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/material/form-field","@angular/material/checkbox","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/input","@angular/common","@angular/platform-browser","@angular/material/select","@angular/material/core","@angular/material/expansion","@shared/components/button/toggle-password.component","@shared/components/file-input.component","@shared/components/queue/queue-type-list.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/cdk/keycodes","@angular/material/chips","@angular/material/icon","@angular/flex-layout/extended","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/material/tooltip","rxjs/operators","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@home/components/public-api","@shared/components/relation/relation-type-autocomplete.component","@shared/components/entity/entity-subtype-list.component","@home/components/relation/relation-filters.component","rxjs","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component"],(function(e){"use strict";var t,o,r,a,n,l,i,s,m,u,p,d,f,c,g,x,y,b,h,C,F,L,v,I,N,T,q,k,M,S,A,G,D,V,E,P,R,w,O,H,U,K,B,j,z,_,Q,$,W,Y,J,Z,X,ee,te,oe,re,ae,ne,le,ie,se,me,ue,pe,de,fe,ce,ge,xe,ye,be,he,Ce,Fe,Le,ve,Ie;return{setters:[function(e){t=e,o=e.Component,r=e.Pipe,a=e.ViewChild,n=e.forwardRef,l=e.Input,i=e.NgModule},function(e){s=e.RuleNodeConfigurationComponent,m=e.AttributeScope,u=e.telemetryTypeTranslations,p=e.ServiceType,d=e.AlarmSeverity,f=e.alarmSeverityTranslations,c=e.EntitySearchDirection,g=e.entitySearchDirectionTranslations,x=e.EntityType,y=e.PageComponent,b=e.MessageType,h=e.messageTypeNames,C=e,F=e.SharedModule,L=e.AggregationType,v=e.aggregationTranslations,I=e.alarmStatusTranslations,N=e.AlarmStatus},function(e){T=e},function(e){q=e,k=e.Validators,M=e.NgControl,S=e.NG_VALUE_ACCESSOR,A=e.NG_VALIDATORS,G=e.FormControl},function(e){D=e},function(e){V=e},function(e){E=e},function(e){P=e},function(e){R=e},function(e){w=e,O=e.CommonModule},function(e){H=e},function(e){U=e},function(e){K=e},function(e){B=e},function(e){j=e},function(e){z=e},function(e){_=e},function(e){Q=e,$=e.isDefinedAndNotNull,W=e.isNotEmptyStr},function(e){Y=e},function(e){J=e},function(e){Z=e.ENTER,X=e.COMMA,ee=e.SEMICOLON},function(e){te=e},function(e){oe=e},function(e){re=e},function(e){ae=e},function(e){ne=e},function(e){le=e.coerceBooleanProperty},function(e){ie=e},function(e){se=e},function(e){me=e.distinctUntilChanged,ue=e.startWith,pe=e.map,de=e.mergeMap,fe=e.share},function(e){ce=e},function(e){ge=e},function(e){xe=e.HomeComponentsModule},function(e){ye=e},function(e){be=e},function(e){he=e},function(e){Ce=e.of},function(e){Fe=e},function(e){Le=e},function(e){ve=e},function(e){Ie=e}],execute:function(){class Ne extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",Ne),Ne.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ne,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ne.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ne,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"
",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ne,decorators:[{type:o,args:[{selector:"tb-node-empty-config",template:"
",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Te{constructor(e){this.sanitizer=e}transform(e){return this.sanitizer.bypassSecurityTrustHtml(e)}}e("SafeHtmlPipe",Te),Te.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Te,deps:[{token:H.DomSanitizer}],target:t.ɵɵFactoryTarget.Pipe}),Te.ɵpipe=t.ɵɵngDeclarePipe({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Te,name:"safeHtml"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Te,decorators:[{type:r,args:[{name:"safeHtml"}]}],ctorParameters:function(){return[{type:H.DomSanitizer}]}});class qe extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[k.required,k.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[k.required,k.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",qe),qe.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qe,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qe.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:qe,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qe,decorators:[{type:o,args:[{selector:"tb-action-node-assign-to-customer-config",templateUrl:"./assign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ke extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[k.required]],notifyDevice:[!e||e.notifyDevice,[]]})}}var Me;e("AttributesConfigComponent",ke),ke.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ke,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ke.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ke,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ke,decorators:[{type:o,args:[{selector:"tb-action-node-attributes-config",templateUrl:"./attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(Me||(Me={}));const Se=new Map([[Me.CUSTOMER,"tb.rulenode.originator-customer"],[Me.TENANT,"tb.rulenode.originator-tenant"],[Me.RELATED,"tb.rulenode.originator-related"],[Me.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);var Ae;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(Ae||(Ae={}));const Ge=new Map([[Ae.CIRCLE,"tb.rulenode.perimeter-circle"],[Ae.POLYGON,"tb.rulenode.perimeter-polygon"]]);var De;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(De||(De={}));const Ve=new Map([[De.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[De.SECONDS,"tb.rulenode.time-unit-seconds"],[De.MINUTES,"tb.rulenode.time-unit-minutes"],[De.HOURS,"tb.rulenode.time-unit-hours"],[De.DAYS,"tb.rulenode.time-unit-days"]]);var Ee;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(Ee||(Ee={}));const Pe=new Map([[Ee.METER,"tb.rulenode.range-unit-meter"],[Ee.KILOMETER,"tb.rulenode.range-unit-kilometer"],[Ee.FOOT,"tb.rulenode.range-unit-foot"],[Ee.MILE,"tb.rulenode.range-unit-mile"],[Ee.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var Re;!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(Re||(Re={}));const we=new Map([[Re.TITLE,"tb.rulenode.entity-details-title"],[Re.COUNTRY,"tb.rulenode.entity-details-country"],[Re.STATE,"tb.rulenode.entity-details-state"],[Re.CITY,"tb.rulenode.entity-details-city"],[Re.ZIP,"tb.rulenode.entity-details-zip"],[Re.ADDRESS,"tb.rulenode.entity-details-address"],[Re.ADDRESS2,"tb.rulenode.entity-details-address2"],[Re.PHONE,"tb.rulenode.entity-details-phone"],[Re.EMAIL,"tb.rulenode.entity-details-email"],[Re.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Oe,He,Ue;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(Oe||(Oe={})),function(e){e.ASC="ASC",e.DESC="DESC"}(He||(He={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(Ue||(Ue={}));const Ke=new Map([[Ue.STANDARD,"tb.rulenode.sqs-queue-standard"],[Ue.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Be=["anonymous","basic","cert.PEM"],je=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),ze=["sas","cert.PEM"],_e=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var Qe;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Qe||(Qe={}));const $e=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],We=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);class Ye extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=ze,this.azureIotHubCredentialsTypeTranslationsMap=_e}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[k.required]],host:[e?e.host:null,[k.required]],port:[e?e.port:null,[k.required,k.min(1),k.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[k.required,k.min(1),k.max(200)]],clientId:[e?e.clientId:null,[k.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[k.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),o=t.get("type").value;switch(e&&t.reset({type:o},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),o){case"sas":t.get("sasKey").setValidators([k.required]);break;case"cert.PEM":t.get("privateKey").setValidators([k.required]),t.get("privateKeyFileName").setValidators([k.required]),t.get("cert").setValidators([k.required]),t.get("certFileName").setValidators([k.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}}e("AzureIotHubConfigComponent",Ye),Ye.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ye,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ye.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ye,selector:"tb-action-node-azure-iot-hub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:B.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:B.MatAccordion,selector:"mat-accordion",inputs:["multi","displayMode","togglePosition","hideToggle"],exportAs:["matAccordion"]},{type:B.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:B.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:q.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:w.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ye,decorators:[{type:o,args:[{selector:"tb-action-node-azure-iot-hub-config",templateUrl:"./azure-iot-hub-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Je extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.serviceType=p.TB_RULE_ENGINE}configForm(){return this.checkPointConfigForm}onConfigurationSet(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[k.required]]})}}e("CheckPointConfigComponent",Je),Je.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Je,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Je.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Je,selector:"tb-action-node-check-point-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',components:[{type:_.QueueTypeListComponent,selector:"tb-queue-type-list",inputs:["required","disabled","queueType"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Je,decorators:[{type:o,args:[{selector:"tb-action-node-check-point-config",templateUrl:"./check-point-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ze extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[k.required]],alarmType:[e?e.alarmType:null,[k.required]]})}testScript(){const e=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(e,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/clear_alarm_node_script_fn").subscribe((e=>{e&&this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",Ze),Ze.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ze,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Ze.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ze,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ze,decorators:[{type:o,args:[{selector:"tb-action-node-clear-alarm-config",templateUrl:"./clear-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class Xe extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r,this.alarmSeverities=Object.keys(d),this.alarmSeverityTranslationMap=f,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[k.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],overwriteAlarmDetails:[!!e&&e.overwriteAlarmDetails,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData"]}updateValidators(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([k.required]),this.createAlarmConfigForm.get("severity").setValidators([k.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})}testScript(){const e=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(e,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/create_alarm_node_script_fn").subscribe((e=>{e&&this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(e)}))}removeKey(e,t){const o=this.createAlarmConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.createAlarmConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}onValidate(){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,t=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;e&&!t||this.jsFuncComponent.validateOnSubmit()}}e("CreateAlarmConfigComponent",Xe),Xe.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xe,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Xe.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Xe,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
\n \n \n \n
\n \n
\n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:re.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xe,decorators:[{type:o,args:[{selector:"tb-action-node-create-alarm-config",templateUrl:"./create-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class et extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[k.required]],entityType:[e?e.entityType:null,[k.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[k.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[k.required,k.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([k.required,k.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==x.DEVICE&&t!==x.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([k.required,k.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}}e("CreateRelationConfigComponent",et),et.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:et,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),et.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:et,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","showLabel","required","disabled"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:et,decorators:[{type:o,args:[{selector:"tb-action-node-create-relation-config",templateUrl:"./create-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class tt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[k.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[k.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[k.required,k.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,o=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([k.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&o?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([k.required,k.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}}e("DeleteRelationConfigComponent",tt),tt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:tt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),tt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:tt,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","showLabel","required","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:tt,decorators:[{type:o,args:[{selector:"tb-action-node-delete-relation-config",templateUrl:"./delete-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ot extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,k.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,k.required]})}}e("DeviceProfileConfigComponent",ot),ot.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ot,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ot.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ot,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ot,decorators:[{type:o,args:[{selector:"tb-device-profile-config",templateUrl:"./device-profile-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class rt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[k.required,k.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[k.required,k.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[k.required]]})}prepareInputConfig(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(){const e=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,"rulenode/generator_node_script_fn").subscribe((e=>{e&&this.generatorConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("GeneratorConfigComponent",rt),rt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:rt,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),rt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:rt,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:ne.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:rt,decorators:[{type:o,args:[{selector:"tb-action-node-generator-config",templateUrl:"./generator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class at extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Ae,this.perimeterTypes=Object.keys(Ae),this.perimeterTypeTranslationMap=Ge,this.rangeUnits=Object.keys(Ee),this.rangeUnitTranslationMap=Pe,this.timeUnits=Object.keys(De),this.timeUnitsTranslationMap=Ve}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[k.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[k.required]],perimeterType:[e?e.perimeterType:null,[k.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[k.required,k.min(1),k.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[k.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[k.required,k.min(1),k.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[k.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([k.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||o!==Ae.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([k.required,k.min(-90),k.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([k.required,k.min(-180),k.max(180)]),this.geoActionConfigForm.get("range").setValidators([k.required,k.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([k.required])),t||o!==Ae.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([k.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoActionConfigComponent",at),at.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:at,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),at.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:at,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:at,decorators:[{type:o,args:[{selector:"tb-action-node-gps-geofencing-config",templateUrl:"./gps-geo-action-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class nt extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.injector=o,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.ngControl=this.injector.get(M),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const o of Object.keys(e))Object.prototype.hasOwnProperty.call(e,o)&&t.push(this.fb.group({key:[o,[k.required]],value:[e[o],[k.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[k.required]],value:["",[k.required]]}))}validate(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigComponent",nt),nt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:nt,deps:[{token:T.Store},{token:P.TranslateService},{token:t.Injector},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),nt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:nt,selector:"tb-kv-map-config",inputs:{disabled:"disabled",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:S,useExisting:n((()=>nt)),multi:!0},{provide:A,useExisting:n((()=>nt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#0000008a;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0;align-self:baseline}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:re.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{type:q.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:se.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:nt,decorators:[{type:o,args:[{selector:"tb-kv-map-config",templateUrl:"./kv-map-config.component.html",styleUrls:["./kv-map-config.component.scss"],providers:[{provide:S,useExisting:n((()=>nt)),multi:!0},{provide:A,useExisting:n((()=>nt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:t.Injector},{type:q.FormBuilder}]},propDecorators:{disabled:[{type:l}],requiredText:[{type:l}],keyText:[{type:l}],keyRequiredText:[{type:l}],valText:[{type:l}],valRequiredText:[{type:l}],hintText:[{type:l}],required:[{type:l}]}});class lt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=$e,this.ToByteStandartCharsetTypeTranslationMap=We}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[k.required]],bootstrapServers:[e?e.bootstrapServers:null,[k.required]],retries:[e?e.retries:null,[k.min(0)]],batchSize:[e?e.batchSize:null,[k.min(0)]],linger:[e?e.linger:null,[k.min(0)]],bufferMemory:[e?e.bufferMemory:null,[k.min(0)]],acks:[e?e.acks:null,[k.required]],keySerializer:[e?e.keySerializer:null,[k.required]],valueSerializer:[e?e.valueSerializer:null,[k.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([k.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",lt),lt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:lt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),lt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:lt,selector:"tb-action-node-kafka-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:lt,decorators:[{type:o,args:[{selector:"tb-action-node-kafka-config",templateUrl:"./kafka-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class it extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/log_node_script_fn").subscribe((e=>{e&&this.logConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",it),it.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:it,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),it.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:it,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:it,decorators:[{type:o,args:[{selector:"tb-action-node-log-config",templateUrl:"./log-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class st extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRquired=!0,this.allCredentialsTypes=Be,this.credentialsTypeTranslationsMap=je,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[k.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(me()).subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const o=e[t];if(!o.firstChange&&o.currentValue!==o.previousValue&&o.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){$(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))}setDisabledState(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([k.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRquired?[k.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(k.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return o=>{t||(t=[Object.keys(o.controls)]);return(null==o?void 0:o.controls)&&t.some((t=>t.every((t=>!e(o.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}}e("CredentialsConfigComponent",st),st.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:st,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),st.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:st,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRquired:"passwordFieldRquired"},providers:[{provide:S,useExisting:n((()=>st)),multi:!0},{provide:A,useExisting:n((()=>st)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:t,template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',components:[{type:B.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:B.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:B.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:B.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:B.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{type:D.MatLabel,selector:"mat-label"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:w.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:w.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:st,decorators:[{type:o,args:[{selector:"tb-credentials-config",templateUrl:"./credentials-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>st)),multi:!0},{provide:A,useExisting:n((()=>st)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]},propDecorators:{required:[{type:l}],disableCertPemCredentials:[{type:l}],passwordFieldRquired:[{type:l}]}});class mt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[]}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[k.required]],host:[e?e.host:null,[k.required]],port:[e?e.port:null,[k.required,k.min(1),k.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[k.required,k.min(1),k.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&W(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]}),this.subscriptions.push(this.mqttConfigForm.get("clientId").valueChanges.subscribe((e=>{W(e)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1})})))}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}}e("MqttConfigComponent",mt),mt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:mt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:mt,selector:"tb-action-node-mqtt-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n
tb.rulenode.client-id-hint
\n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:st,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:mt,decorators:[{type:o,args:[{selector:"tb-action-node-mqtt-config",templateUrl:"./mqtt-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ut extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[k.required,k.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[k.required]]})}}e("MsgCountConfigComponent",ut),ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ut,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ut,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ut,decorators:[{type:o,args:[{selector:"tb-action-node-msg-count-config",templateUrl:"./msg-count-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class pt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[k.required,k.min(1),k.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([k.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([k.required,k.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",pt),pt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:pt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),pt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:pt,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:pt,decorators:[{type:o,args:[{selector:"tb-action-node-msg-delay-config",templateUrl:"./msg-delay-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class dt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[k.required]],topicName:[e?e.topicName:null,[k.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[k.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[k.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",dt),dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:dt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:dt,selector:"tb-action-node-pub-sub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:dt,decorators:[{type:o,args:[{selector:"tb-action-node-pub-sub-config",templateUrl:"./pubsub-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ft extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[k.required]]})}}e("PushToCloudConfigComponent",ft),ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ft,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ft,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ft,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-cloud-config",templateUrl:"./push-to-cloud-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ct extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[k.required]]})}}e("PushToEdgeConfigComponent",ct),ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ct,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ct,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ct,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-edge-config",templateUrl:"./push-to-edge-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class gt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[k.required]],port:[e?e.port:null,[k.required,k.min(1),k.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[k.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[k.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",gt),gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:gt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:gt,selector:"tb-action-node-rabbit-mq-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:gt,decorators:[{type:o,args:[{selector:"tb-action-node-rabbit-mq-config",templateUrl:"./rabbit-mq-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class xt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys(Qe)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[k.required]],requestMethod:[e?e.requestMethod:null,[k.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[k.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,o=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[k.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[k.required,k.min(1),k.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([k.min(0)])),o?this.restApiCallConfigForm.get("maxQueueSize").setValidators([k.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}}e("RestApiCallConfigComponent",xt),xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:xt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:xt,selector:"tb-action-node-rest-api-call-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:st,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:xt,decorators:[{type:o,args:[{selector:"tb-action-node-rest-api-call-config",templateUrl:"./rest-api-call-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class yt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}}e("RpcReplyConfigComponent",yt),yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:yt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:yt,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:yt,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-reply-config",templateUrl:"./rpc-reply-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class bt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[k.required,k.min(0)]]})}}e("RpcRequestConfigComponent",bt),bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:bt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:bt,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:bt,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-request-config",templateUrl:"./rpc-request-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class ht extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[k.required,k.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[k.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",ht),ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ht,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:ht,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ht,decorators:[{type:o,args:[{selector:"tb-action-node-custom-table-config",templateUrl:"./save-to-custom-table-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ct extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,o=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([k.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([k.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([k.required,k.min(1),k.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([k.required,k.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(o?[k.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(o?[k.required,k.min(1),k.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}}e("SendEmailConfigComponent",Ct),Ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ct,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ct,selector:"tb-action-node-send-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ce.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ct,decorators:[{type:o,args:[{selector:"tb-action-node-send-email-config",templateUrl:"./send-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ft extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[k.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[k.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([k.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",Ft),Ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ft,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ft,selector:"tb-action-node-send-sms-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:ge.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ft,decorators:[{type:o,args:[{selector:"tb-action-node-send-sms-config",templateUrl:"./send-sms-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Lt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[k.required]],accessKeyId:[e?e.accessKeyId:null,[k.required]],secretAccessKey:[e?e.secretAccessKey:null,[k.required]],region:[e?e.region:null,[k.required]]})}}e("SnsConfigComponent",Lt),Lt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Lt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Lt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Lt,selector:"tb-action-node-sns-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Lt,decorators:[{type:o,args:[{selector:"tb-action-node-sns-config",templateUrl:"./sns-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class vt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=Ue,this.sqsQueueTypes=Object.keys(Ue),this.sqsQueueTypeTranslationsMap=Ke}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[k.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[k.required]],delaySeconds:[e?e.delaySeconds:null,[k.min(0),k.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[k.required]],secretAccessKey:[e?e.secretAccessKey:null,[k.required]],region:[e?e.region:null,[k.required]]})}}e("SqsConfigComponent",vt),vt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:vt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),vt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:vt,selector:"tb-action-node-sqs-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:vt,decorators:[{type:o,args:[{selector:"tb-action-node-sqs-config",templateUrl:"./sqs-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class It extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[k.required,k.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",It),It.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:It,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),It.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:It,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:It,decorators:[{type:o,args:[{selector:"tb-action-node-timeseries-config",templateUrl:"./timeseries-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Nt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[k.required,k.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[k.required,k.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",Nt),Nt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Nt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Nt,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Nt,decorators:[{type:o,args:[{selector:"tb-action-node-un-assign-to-customer-config",templateUrl:"./unassign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Tt extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[k.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[k.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}}e("DeviceRelationsQueryConfigComponent",Tt),Tt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Tt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Tt,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:S,useExisting:n((()=>Tt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ye.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]},{type:be.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","disabled","entityType"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Tt,decorators:[{type:o,args:[{selector:"tb-device-relations-query-config",templateUrl:"./device-relations-query-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>Tt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class qt extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[k.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigComponent",qt),qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:qt,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:S,useExisting:n((()=>qt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:he.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:qt,decorators:[{type:o,args:[{selector:"tb-relations-query-config",templateUrl:"./relations-query-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>qt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class kt extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.truncate=o,this.fb=r,this.placeholder="tb.rulenode.message-type",this.separatorKeysCodes=[Z,X,ee],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(b))this.messageTypesList.push({name:h.get(b[e]),value:e})}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(ue(""),pe((e=>e||"")),de((e=>this.fetchMessageTypes(e))),fe())}ngAfterViewInit(){}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return!!(e&&null!=e&&e.length>0)}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ce(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ce(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t=null;const o=e.trim(),r=this.messageTypesList.find((e=>e.name===o));t=r?{name:r.name,value:r.value}:{name:o,value:o},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}}e("MessageTypesConfigComponent",kt),kt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:kt,deps:[{token:T.Store},{token:P.TranslateService},{token:C.TruncatePipe},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),kt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:kt,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:S,useExisting:n((()=>kt)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Fe.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Fe.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:Fe.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,highlight:Le.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:kt,decorators:[{type:o,args:[{selector:"tb-message-types-config",templateUrl:"./message-types-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>kt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:C.TruncatePipe},{type:q.FormBuilder}]},propDecorators:{required:[{type:l}],label:[{type:l}],placeholder:[{type:l}],disabled:[{type:l}],chipList:[{type:a,args:["chipList",{static:!1}]}],matAutocomplete:[{type:a,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:a,args:["messageTypeInput",{static:!1}]}]}});class Mt{}e("RulenodeCoreConfigCommonModule",Mt),Mt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Mt.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,declarations:[nt,Tt,qt,kt,st,Te],imports:[O,F,xe],exports:[nt,Tt,qt,kt,st,Te]}),Mt.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,imports:[[O,F,xe]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Mt,decorators:[{type:i,args:[{declarations:[nt,Tt,qt,kt,st,Te],imports:[O,F,xe],exports:[nt,Tt,qt,kt,st,Te]}]}]});class St{}e("RuleNodeCoreConfigActionModule",St),St.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,deps:[],target:t.ɵɵFactoryTarget.NgModule}),St.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,declarations:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft],imports:[O,F,xe,Mt],exports:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft]}),St.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,imports:[[O,F,xe,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:St,decorators:[{type:i,args:[{declarations:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft],imports:[O,F,xe,Mt],exports:[ke,It,bt,it,qe,Ze,Xe,et,pt,tt,rt,at,ut,yt,ht,Nt,Lt,vt,dt,lt,mt,gt,xt,Ct,Je,Ye,ot,Ft,ct,ft]}]}]});class At extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[k.required]],outputValueKey:[e?e.outputValueKey:null,[k.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[k.min(0),k.max(15)]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([k.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",At),At.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:At,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),At.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:At,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:t,template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:At,decorators:[{type:o,args:[{selector:"tb-enrichment-node-calculate-delta-config",templateUrl:"./calculate-delta-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Gt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.customerAttributesConfigForm}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[k.required]]})}}e("CustomerAttributesConfigComponent",Gt),Gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Gt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Gt,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Gt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-customer-attributes-config",templateUrl:"./customer-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Dt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[k.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})}removeKey(e,t){const o=this.deviceAttributesConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.deviceAttributesConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.deviceAttributesConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.deviceAttributesConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("DeviceAttributesConfigComponent",Dt),Dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Dt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Dt,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:Tt,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Dt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-device-attributes-config",templateUrl:"./device-attributes-config.component.html",styleUrls:["./device-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Vt extends s{constructor(e,t,o){super(e),this.store=e,this.translate=t,this.fb=o,this.entityDetailsTranslationsMap=we,this.entityDetailsList=[],this.searchText="",this.displayDetailsFn=this.displayDetails.bind(this);for(const e of Object.keys(Re))this.entityDetailsList.push(Re[e]);this.detailsFormControl=new G(""),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(ue(""),pe((e=>e||"")),de((e=>this.fetchEntityDetails(e))),fe())}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[k.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})}displayDetails(e){return e?this.translate.instant(we.get(e)):void 0}fetchEntityDetails(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ce(this.entityDetailsList.filter((t=>this.translate.instant(we.get(Re[t])).toUpperCase().includes(e))))}return Ce(this.entityDetailsList)}detailsFieldSelected(e){this.addDetailsField(e.option.value),this.clear("")}removeDetailsField(e){const t=this.entityDetailsConfigForm.get("detailsList").value;if(t){const o=t.indexOf(e);o>=0&&(t.splice(o,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}}addDetailsField(e){let t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))}onEntityDetailsInputFocus(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}}e("EntityDetailsConfigComponent",Vt),Vt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Vt,deps:[{token:T.Store},{token:P.TranslateService},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Vt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Vt,selector:"tb-enrichment-node-entity-details-config",viewQueries:[{propertyName:"detailsInput",first:!0,predicate:["detailsInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Fe.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Fe.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:Fe.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,highlight:Le.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Vt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-entity-details-config",templateUrl:"./entity-details-config.component.html",styleUrls:["./entity-details-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:q.FormBuilder}]},propDecorators:{detailsInput:[{type:a,args:["detailsInput",{static:!1}]}]}});class Et extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee],this.aggregationTypes=L,this.aggregations=Object.keys(L),this.aggregationTypesTranslations=v,this.fetchMode=Oe,this.fetchModes=Object.keys(Oe),this.samplingOrders=Object.keys(He),this.timeUnits=Object.values(De),this.timeUnitsTranslationMap=Ve}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],aggregation:[e?e.aggregation:null,[k.required]],fetchMode:[e?e.fetchMode:null,[k.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,o=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===Oe.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([k.required,k.min(2),k.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),o?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([k.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([k.required,k.min(1),k.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([k.required,k.min(1),k.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([k.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const o=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("GetTelemetryFromDatabaseConfigComponent",Et),Et.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Et,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Et.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Et,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n aggregation.function\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Et,decorators:[{type:o,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",templateUrl:"./get-telemetry-from-database-config.component.html",styleUrls:["./get-telemetry-from-database-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Pt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})}removeKey(e,t){const o=this.originatorAttributesConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.originatorAttributesConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.originatorAttributesConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.originatorAttributesConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("OriginatorAttributesConfigComponent",Pt),Pt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Pt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Pt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Pt,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Pt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-attributes-config",templateUrl:"./originator-attributes-config.component.html",styleUrls:["./originator-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Rt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.originatorFieldsConfigForm}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[k.required]]})}}e("OriginatorFieldsConfigComponent",Rt),Rt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Rt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Rt,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n',components:[{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Rt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-fields-config",templateUrl:"./originator-fields-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class wt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.relatedAttributesConfigForm}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[k.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[k.required]]})}}e("RelatedAttributesConfigComponent",wt),wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:wt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:wt,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:qt,selector:"tb-relations-query-config",inputs:["disabled","required"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:wt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-related-attributes-config",templateUrl:"./related-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ot extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.tenantAttributesConfigForm}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[k.required]]})}}e("TenantAttributesConfigComponent",Ot),Ot.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ot,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ot.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ot,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:nt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ot,decorators:[{type:o,args:[{selector:"tb-enrichment-node-tenant-attributes-config",templateUrl:"./tenant-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Ht{}e("RulenodeCoreConfigEnrichmentModule",Ht),Ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Ht.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,declarations:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At],imports:[O,F,Mt],exports:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At]}),Ht.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ht,decorators:[{type:i,args:[{declarations:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At],imports:[O,F,Mt],exports:[Gt,Vt,Dt,Pt,Rt,Et,wt,Ot,At]}]}]});class Ut extends s{constructor(e,t,o){super(e),this.store=e,this.translate=t,this.fb=o,this.alarmStatusTranslationsMap=I,this.alarmStatusList=[],this.searchText="",this.displayStatusFn=this.displayStatus.bind(this);for(const e of Object.keys(N))this.alarmStatusList.push(N[e]);this.statusFormControl=new G(""),this.filteredAlarmStatus=this.statusFormControl.valueChanges.pipe(ue(""),pe((e=>e||"")),de((e=>this.fetchAlarmStatus(e))),fe())}ngOnInit(){super.ngOnInit()}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[k.required]]})}displayStatus(e){return e?this.translate.instant(I.get(e)):void 0}fetchAlarmStatus(e){const t=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ce(t.filter((t=>this.translate.instant(I.get(N[t])).toUpperCase().includes(e))))}return Ce(t)}alarmStatusSelected(e){this.addAlarmStatus(e.option.value),this.clear("")}removeAlarmStatus(e){const t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){const o=t.indexOf(e);o>=0&&(t.splice(o,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}}addAlarmStatus(e){let t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}getAlarmStatusList(){return this.alarmStatusList.filter((e=>-1===this.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(e)))}onAlarmStatusInputFocus(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.alarmStatusInput.nativeElement.blur(),this.alarmStatusInput.nativeElement.focus()}),0)}}e("CheckAlarmStatusComponent",Ut),Ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ut,deps:[{token:T.Store},{token:P.TranslateService},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ut,selector:"tb-filter-node-check-alarm-status-config",viewQueries:[{propertyName:"alarmStatusInput",first:!0,predicate:["alarmStatusInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Fe.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Fe.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:Fe.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe,async:w.AsyncPipe,highlight:Le.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ut,decorators:[{type:o,args:[{selector:"tb-filter-node-check-alarm-status-config",templateUrl:"./check-alarm-status.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:q.FormBuilder}]},propDecorators:{alarmStatusInput:[{type:a,args:["alarmStatusInput",{static:!1}]}]}});class Kt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[Z,X,ee]}configForm(){return this.checkMessageConfigForm}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})}validateConfig(){const e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0}removeMessageName(e){const t=this.checkMessageConfigForm.get("messageNames").value,o=t.indexOf(e);o>=0&&(t.splice(o,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))}removeMetadataName(e){const t=this.checkMessageConfigForm.get("metadataNames").value,o=t.indexOf(e);o>=0&&(t.splice(o,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))}addMessageName(e){const t=e.input;let o=e.value;if((o||"").trim()){o=o.trim();let e=this.checkMessageConfigForm.get("messageNames").value;e&&-1!==e.indexOf(o)||(e||(e=[]),e.push(o),this.checkMessageConfigForm.get("messageNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}addMetadataName(e){const t=e.input;let o=e.value;if((o||"").trim()){o=o.trim();let e=this.checkMessageConfigForm.get("metadataNames").value;e&&-1!==e.indexOf(o)||(e||(e=[]),e.push(o),this.checkMessageConfigForm.get("metadataNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("CheckMessageConfigComponent",Kt),Kt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Kt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Kt,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Kt,decorators:[{type:o,args:[{selector:"tb-filter-node-check-message-config",templateUrl:"./check-message-config.component.html",styleUrls:["./check-message-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Bt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.keys(c),this.entitySearchDirectionTranslationsMap=g}configForm(){return this.checkRelationConfigForm}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[k.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[k.required]:[]],relationType:[e?e.relationType:null,[k.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[k.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[k.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",Bt),Bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Bt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Bt,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n',components:[{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","showLabel","required","disabled"]},{type:ve.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]},{type:ye.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Bt,decorators:[{type:o,args:[{selector:"tb-filter-node-check-relation-config",templateUrl:"./check-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class jt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Ae,this.perimeterTypes=Object.keys(Ae),this.perimeterTypeTranslationMap=Ge,this.rangeUnits=Object.keys(Ee),this.rangeUnitTranslationMap=Pe}configForm(){return this.geoFilterConfigForm}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[k.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[k.required]],perimeterType:[e?e.perimeterType:null,[k.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([k.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||o!==Ae.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([k.required,k.min(-90),k.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([k.required,k.min(-180),k.max(180)]),this.geoFilterConfigForm.get("range").setValidators([k.required,k.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([k.required])),t||o!==Ae.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([k.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoFilterConfigComponent",jt),jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:jt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:jt,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:E.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:q.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:q.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:q.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:jt,decorators:[{type:o,args:[{selector:"tb-filter-node-gps-geofencing-config",templateUrl:"./gps-geo-filter-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class zt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[k.required]]})}}e("MessageTypeConfigComponent",zt),zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:zt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:zt,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n
\n',components:[{type:kt,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:zt,decorators:[{type:o,args:[{selector:"tb-filter-node-message-type-config",templateUrl:"./message-type-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class _t extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[x.DEVICE,x.ASSET,x.ENTITY_VIEW,x.TENANT,x.CUSTOMER,x.USER,x.DASHBOARD,x.RULE_CHAIN,x.RULE_NODE]}configForm(){return this.originatorTypeConfigForm}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[k.required]]})}}e("OriginatorTypeConfigComponent",_t),_t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:_t,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),_t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:_t,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}\n"],components:[{type:Ie.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","disabled","allowedEntityTypes","ignoreAuthorityFilter"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:E.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:_t,decorators:[{type:o,args:[{selector:"tb-filter-node-originator-type-config",templateUrl:"./originator-type-config.component.html",styleUrls:["./originator-type-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Qt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/filter_node_script_fn").subscribe((e=>{e&&this.scriptConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",Qt),Qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Qt,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Qt,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Qt,decorators:[{type:o,args:[{selector:"tb-filter-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class $t extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/switch_node_script_fn").subscribe((e=>{e&&this.switchConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",$t),$t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:$t,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),$t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:$t,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:$t,decorators:[{type:o,args:[{selector:"tb-filter-node-switch-config",templateUrl:"./switch-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class Wt{}e("RuleNodeCoreConfigFilterModule",Wt),Wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Wt.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,declarations:[Kt,Bt,jt,zt,_t,Qt,$t,Ut],imports:[O,F,Mt],exports:[Kt,Bt,jt,zt,_t,Qt,$t,Ut]}),Wt.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Wt,decorators:[{type:i,args:[{declarations:[Kt,Bt,jt,zt,_t,Qt,$t,Ut],imports:[O,F,Mt],exports:[Kt,Bt,jt,zt,_t,Qt,$t,Ut]}]}]});class Yt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=Me,this.originatorSources=Object.keys(Me),this.originatorSourceTranslationMap=Se}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[k.required]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===Me.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([k.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})}}e("ChangeOriginatorConfigComponent",Yt),Yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Yt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Yt,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:qt,selector:"tb-relations-query-config",inputs:["disabled","required"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Yt,decorators:[{type:o,args:[{selector:"tb-transformation-node-change-originator-config",templateUrl:"./change-originator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Jt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[k.required]]})}testScript(){const e=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/transformation_node_script_fn").subscribe((e=>{e&&this.scriptConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",Jt),Jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Jt,deps:[{token:T.Store},{token:q.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Jt,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:Y.JsFuncComponent,selector:"tb-js-func",inputs:["functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Jt,decorators:[{type:o,args:[{selector:"tb-transformation-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class Zt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",value:"false"},{name:"tb.mail-body-type.html",value:"true"},{name:"tb.mail-body-type.dynamic",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[k.required]],toTemplate:[e?e.toTemplate:null,[k.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[k.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null],bodyTemplate:[e?e.bodyTemplate:null,[k.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.pipe(ue([null==e?void 0:e.subjectTemplate])).subscribe((e=>{"dynamic"===e?(this.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").setValidators(k.required)):this.toEmailConfigForm.get("isHtmlTemplate").clearValidators(),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity()}))}}e("ToEmailConfigComponent",Zt),Zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Zt,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Zt,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:q.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe,safeHtml:Te}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Zt,decorators:[{type:o,args:[{selector:"tb-transformation-node-to-email-config",templateUrl:"./to-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class Xt{}e("RulenodeCoreConfigTransformModule",Xt),Xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Xt.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,declarations:[Yt,Jt,Zt],imports:[O,F,Mt],exports:[Yt,Jt,Zt]}),Xt.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Xt,decorators:[{type:i,args:[{declarations:[Yt,Jt,Zt],imports:[O,F,Mt],exports:[Yt,Jt,Zt]}]}]});class eo extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=x}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[k.required]]})}}e("RuleChainInputComponent",eo),eo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:eo,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),eo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:eo,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',components:[{type:ve.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]}],directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:q.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:q.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:q.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:eo,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-input-config",templateUrl:"./rule-chain-input.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class to extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",to),to.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:to,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),to.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:to,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n',directives:[{type:E.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:q.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:q.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:to,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-output-config",templateUrl:"./rule-chain-output.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:q.FormBuilder}]}});class oo{}e("RuleNodeCoreConfigFlowModule",oo),oo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,deps:[],target:t.ɵɵFactoryTarget.NgModule}),oo.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,declarations:[eo,to],imports:[O,F,Mt],exports:[eo,to]}),oo.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,imports:[[O,F,Mt]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:oo,decorators:[{type:i,args:[{declarations:[eo,to],imports:[O,F,Mt],exports:[eo,to]}]}]});class ro{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","timeseries-key":"Timeseries key","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","client-id-hint":'Hint: Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Hint: Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-key-name":"Perimeter key name","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"alarm-severity-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body to substitute "Source" and "Target" key names'},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"},"mail-body-type":{"plain-text":"Plain Text",html:"HTML",dynamic:"Dynamic"}}},!0)}(e)}}e("RuleNodeCoreConfigModule",ro),ro.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,deps:[{token:P.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),ro.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,declarations:[Ne],imports:[O,F],exports:[St,Wt,Ht,Xt,oo,Ne]}),ro.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,imports:[[O,F],St,Wt,Ht,Xt,oo]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:ro,decorators:[{type:i,args:[{declarations:[Ne],imports:[O,F],exports:[St,Wt,Ht,Xt,oo,Ne]}]}],ctorParameters:function(){return[{type:P.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 78caee289f..d9706f6b96 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -57,6 +57,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.anyLong; +import static org.mockito.Mockito.atMost; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.same; @@ -513,6 +514,70 @@ public class TbAlarmNodeTest { assertEquals(expectedAlarm, actualAlarm); } + @Test + public void testCreateAlarmsWithPropagationToTenantWithDynamicTypes() throws Exception{ + for (int i = 0; i < 10; i++) { + var config = new TbCreateAlarmNodeConfiguration(); + config.setPropagateToTenant(true); + config.setSeverity(CRITICAL.name()); + config.setAlarmType("SomeType" + i); + config.setAlarmDetailsBuildJs("DETAILS"); + config.setDynamicSeverity(true); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + + when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + + when(ctx.getTenantId()).thenReturn(tenantId); + when(ctx.getAlarmService()).thenReturn(alarmService); + when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); + + node = new TbCreateAlarmNode(); + node.init(ctx, nodeConfiguration); + + metaData.putValue("key", "value"); + TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); + when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType" + i)).thenReturn(Futures.immediateFuture(null)); + doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class)); + long ts = msg.getTs(); + node.onMsg(ctx, msg); + + verify(ctx, atMost(10)).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx, atMost(10)).tellNext(any(), eq("Created")); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx, atMost(10)).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + + assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals("value", metadataCaptor.getValue().getValue("key")); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertNotSame(metaData, metadataCaptor.getValue()); + + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class); + Alarm expectedAlarm = Alarm.builder() + .startTs(ts) + .endTs(ts) + .tenantId(tenantId) + .originator(originator) + .status(ACTIVE_UNACK) + .severity(CRITICAL) + .propagateToTenant(true) + .type("SomeType" + i) + .details(null) + .build(); + + assertEquals(expectedAlarm, actualAlarm); + } + } + private void initWithCreateAlarmScript() { try { TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java new file mode 100644 index 0000000000..fd1a5af6c1 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java @@ -0,0 +1,80 @@ +/** + * Copyright © 2016-2022 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.rule.engine.action; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.Collections; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; + +@Slf4j +public class TbLogNodeTest { + + @Test + void givenMsg_whenToLog_thenReturnString() { + TbLogNode node = new TbLogNode(); + String data = "{\"key\": \"value\"}"; + TbMsgMetaData metaData = new TbMsgMetaData(Map.of("mdKey1", "mdValue1", "mdKey2", "23")); + TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, data); + + String logMessage = node.toLogMessage(msg); + log.info(logMessage); + + assertThat(logMessage).isEqualTo("\n" + + "Incoming message:\n" + + "{\"key\": \"value\"}\n" + + "Incoming metadata:\n" + + "{\"mdKey1\":\"mdValue1\",\"mdKey2\":\"23\"}"); + } + + @Test + void givenEmptyDataMsg_whenToLog_thenReturnString() { + TbLogNode node = new TbLogNode(); + TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap()); + TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, ""); + + String logMessage = node.toLogMessage(msg); + log.info(logMessage); + + assertThat(logMessage).isEqualTo("\n" + + "Incoming message:\n" + + "\n" + + "Incoming metadata:\n" + + "{}"); + } + @Test + void givenNullDataMsg_whenToLog_thenReturnString() { + TbLogNode node = new TbLogNode(); + TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap()); + TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, null); + + String logMessage = node.toLogMessage(msg); + log.info(logMessage); + + assertThat(logMessage).isEqualTo("\n" + + "Incoming message:\n" + + "null\n" + + "Incoming metadata:\n" + + "{}"); + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java new file mode 100644 index 0000000000..b7dbda1780 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/edge/TbMsgPushToEdgeNodeTest.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2022 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.rule.engine.edge; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.id.DeviceId; +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.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.dao.edge.EdgeService; + +import java.util.UUID; + +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class TbMsgPushToEdgeNodeTest { + + TbMsgPushToEdgeNode node; + + private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); + private final DeviceId deviceId = new DeviceId(UUID.randomUUID()); + + @Mock + private TbContext ctx; + + @Mock + private EdgeService edgeService; + + @Before + public void setUp() throws TbNodeException { + node = new TbMsgPushToEdgeNode(); + TbMsgPushToEdgeNodeConfiguration config = new TbMsgPushToEdgeNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @Test + public void ackMsgInCaseNoEdgeRelated() { + Mockito.when(ctx.getTenantId()).thenReturn(tenantId); + Mockito.when(ctx.getEdgeService()).thenReturn(edgeService); + Mockito.when(edgeService.findRelatedEdgeIdsByEntityId(tenantId, deviceId, new PageLink(TbMsgPushToEdgeNode.DEFAULT_PAGE_SIZE))).thenReturn(new PageData<>()); + + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, "{}", null, null); + + node.onMsg(ctx, msg); + + verify(ctx).ack(msg); + } +} diff --git a/tools/pom.xml b/tools/pom.xml index e6162638bf..802d479890 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard tools diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml index e85779b082..52569eed4a 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 2e41660682..51fc2a6cef 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index 89698919cc..adf3baba30 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 1d6f7ef8eb..8691e5db25 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index 01cd47f931..267540e242 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 5cf7168a57..64a7ff0f29 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT transport diff --git a/ui-ngx/.yarnrc b/ui-ngx/.yarnrc new file mode 100644 index 0000000000..a3a3d23ec6 --- /dev/null +++ b/ui-ngx/.yarnrc @@ -0,0 +1 @@ +network-timeout 1000000 diff --git a/ui-ngx/generate-types.js b/ui-ngx/generate-types.js index a24673cbd5..68b31d6568 100644 --- a/ui-ngx/generate-types.js +++ b/ui-ngx/generate-types.js @@ -17,9 +17,12 @@ const child_process = require("child_process"); const fs = require('fs'); const path = require('path'); -const typeDir = './target/types'; -const srcDir = typeDir + '/src'; -const moduleMapPath = "src/app/modules/common/modules-map.ts"; + +const typeDir = path.join('.', 'target', 'types'); +const srcDir = path.join('.', 'target', 'types', 'src'); +const moduleMapPath = path.join('src', 'app', 'modules', 'common', 'modules-map.ts'); +const ngcPath = path.join('.', 'node_modules', '.bin', 'ngc'); +const tsconfigPath = path.join('src', 'tsconfig.app.json'); console.log(`Remove directory: ${typeDir}`); try { @@ -28,7 +31,7 @@ try { console.error(`Remove directory error: ${err}`); } -const cliCommand = `./node_modules/.bin/ngc --p src/tsconfig.app.json --declaration --outDir ${srcDir}`; +const cliCommand = `${ngcPath} --p ${tsconfigPath} --declaration --outDir ${srcDir}`; console.log(cliCommand); try { child_process.execSync(cliCommand); diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 07e051ad05..a6e28516a3 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,6 +1,6 @@ { "name": "thingsboard", - "version": "3.3.4", + "version": "3.4.0", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", @@ -46,8 +46,8 @@ "canvas-gauges": "^2.1.7", "core-js": "^3.19.2", "date-fns": "^2.26.0", - "flot": "git://github.com/thingsboard/flot.git#0.9-work", - "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", + "flot": "https://github.com/thingsboard/flot.git#0.9-work", + "flot.curvedlines": "https://github.com/MichaelZinsmaier/CurvedLines.git#master", "font-awesome": "^4.7.0", "html2canvas": "^1.3.3", "jquery": "^3.6.0", @@ -69,7 +69,7 @@ "ngx-color-picker": "^11.0.0", "ngx-daterangepicker-material": "^5.0.1", "ngx-drag-drop": "^2.0.0", - "ngx-flowchart": "git://github.com/thingsboard/ngx-flowchart.git#release/1.0.0", + "ngx-flowchart": "https://github.com/thingsboard/ngx-flowchart.git#release/1.0.0", "ngx-hm-carousel": "^2.0.1", "ngx-markdown": "^12.0.1", "ngx-sharebuttons": "^9.0.0", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index 8d5a12cd1a..66bf79951f 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.3.4-SNAPSHOT + 3.4.0-SNAPSHOT thingsboard org.thingsboard diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index c4ff3b8d64..b62bd18b31 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -49,7 +49,8 @@ import Timeout = NodeJS.Timeout; declare type DataKeyFunction = (time: number, prevValue: any) => any; declare type DataKeyPostFunction = (time: number, value: any, prevValue: any, timePrev: number, prevOrigValue: any) => any; -declare type DataUpdatedCb = (data: DataSetHolder, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; +declare type DataUpdatedCb = (data: DataSetHolder, dataIndex: number, + dataKeyIndex: number, detectChanges: boolean, isLatest: boolean) => void; export interface SubscriptionDataKey { name: string; @@ -59,8 +60,10 @@ export interface SubscriptionDataKey { postFuncBody: string; postFunc?: DataKeyPostFunction; index?: number; + listIndex?: number; key?: string; lastUpdateTime?: number; + latest?: boolean; } export interface EntityDataSubscriptionOptions { @@ -104,9 +107,11 @@ export class EntityDataSubscription { private entityIdToDataIndex: {[id: string]: number}; private frequency: number; + private latestFrequency: number; private tickScheduledTime = 0; private tickElapsed = 0; - private timer: Timeout; + private timeseriesTimer: Timeout; + private latestTimer: Timeout; private dataResolved = false; private started = false; @@ -135,9 +140,9 @@ export class EntityDataSubscription { if (this.datasourceType === DatasourceType.entity || this.datasourceType === DatasourceType.entityCount || this.entityDataSubscriptionOptions.type === widgetType.timeseries) { if (this.datasourceType === DatasourceType.function) { - key = `${dataKey.name}_${dataKey.index}_${dataKey.type}`; + key = `${dataKey.name}_${dataKey.index}_${dataKey.type}${dataKey.latest ? '_latest' : ''}`; } else { - key = `${dataKey.name}_${dataKey.type}`; + key = `${dataKey.name}_${dataKey.type}${dataKey.latest ? '_latest' : ''}`; } let dataKeysList = this.dataKeys[key] as Array; if (!dataKeysList) { @@ -145,6 +150,7 @@ export class EntityDataSubscription { this.dataKeys[key] = dataKeysList; } dataKeysList.push(dataKey); + dataKey.listIndex = dataKeysList.length - 1; } else { key = String(objectHashCode(dataKey)); this.dataKeys[key] = dataKey; @@ -154,9 +160,13 @@ export class EntityDataSubscription { } public unsubscribe() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; + if (this.timeseriesTimer) { + clearTimeout(this.timeseriesTimer); + this.timeseriesTimer = null; + } + if (this.latestTimer) { + clearTimeout(this.latestTimer); + this.latestTimer = null; } if (this.datasourceType === DatasourceType.entity || this.datasourceType === DatasourceType.entityCount) { if (this.subscriber) { @@ -213,11 +223,20 @@ export class EntityDataSubscription { dataKey => ({ type: EntityKeyType.ATTRIBUTE, key: dataKey.name }) ); - this.tsFields = this.entityDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.timeseries).map( - dataKey => ({ type: EntityKeyType.TIME_SERIES, key: dataKey.name }) + this.tsFields = this.entityDataSubscriptionOptions.dataKeys. + filter(dataKey => dataKey.type === DataKeyType.timeseries && !dataKey.latest).map( + dataKey => ({ type: EntityKeyType.TIME_SERIES, key: dataKey.name }) ); - this.latestValues = this.attrFields.concat(this.tsFields); + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { + const latestTsFields = this.entityDataSubscriptionOptions.dataKeys. + filter(dataKey => dataKey.type === DataKeyType.timeseries && dataKey.latest).map( + dataKey => ({ type: EntityKeyType.TIME_SERIES, key: dataKey.name }) + ); + this.latestValues = this.attrFields.concat(latestTsFields); + } else { + this.latestValues = this.attrFields.concat(this.tsFields); + } this.subscriber = new TelemetrySubscriber(this.telemetryService); this.dataCommand = new EntityDataCmd(); @@ -467,6 +486,11 @@ export class EntityDataSubscription { }; } } + if (this.latestValues.length > 0) { + cmd.latestCmd = { + keys: this.latestValues + }; + } } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { if (this.latestValues.length > 0) { cmd.latestCmd = { @@ -478,21 +502,22 @@ export class EntityDataSubscription { private startFunction() { this.frequency = 1000; + this.latestFrequency = 1000; if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { this.frequency = Math.min(this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.interval, 5000); } this.tickScheduledTime = this.utils.currentPerfTime(); - if (this.history) { - this.onTick(true); - } else { - this.timer = setTimeout(this.onTick.bind(this, true), 0); - } + this.generateData(true); } private prepareData() { - if (this.timer) { - clearTimeout(this.timer); - this.timer = null; + if (this.timeseriesTimer) { + clearTimeout(this.timeseriesTimer); + this.timeseriesTimer = null; + } + if (this.latestTimer) { + clearTimeout(this.latestTimer); + this.latestTimer = null; } if (this.dataAggregators) { @@ -509,19 +534,23 @@ export class EntityDataSubscription { for (const key of Object.keys(this.dataKeys)) { const dataKeysList = this.dataKeys[key] as Array; dataKeysList.forEach((subscriptionDataKey) => { - tsKeyNames.push(`${subscriptionDataKey.name}_${subscriptionDataKey.index}`); + if (!subscriptionDataKey.latest) { + tsKeyNames.push(`${subscriptionDataKey.name}_${subscriptionDataKey.index}`); + } }); } } else { tsKeyNames = this.tsFields ? this.tsFields.map(field => field.key) : []; } for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) { - if (this.datasourceType === DatasourceType.function) { - this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, tsKeyNames, - DataKeyType.function, dataIndex, this.notifyListener.bind(this)); - } else if (tsKeyNames.length) { - this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, tsKeyNames, - DataKeyType.timeseries, dataIndex, this.notifyListener.bind(this)); + if (tsKeyNames.length) { + if (this.datasourceType === DatasourceType.function) { + this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, tsKeyNames, + DataKeyType.function, dataIndex, this.notifyListener.bind(this)); + } else { + this.dataAggregators[dataIndex] = this.createRealtimeDataAggregator(this.subsTw, tsKeyNames, + DataKeyType.timeseries, dataIndex, this.notifyListener.bind(this)); + } } } } @@ -540,7 +569,8 @@ export class EntityDataSubscription { this.entityDataSubscriptionOptions.type === widgetType.timeseries) { const dataKeysList = dataKey as Array; for (let index = 0; index < dataKeysList.length; index++) { - this.datasourceData[dataIndex][key + '_' + index] = { + const datasourceKey = `${key}_${index}`; + this.datasourceData[dataIndex][datasourceKey] = { data: [] }; } @@ -637,19 +667,21 @@ export class EntityDataSubscription { } } - private notifyListener(data: DataSetHolder, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) { + private notifyListener(data: DataSetHolder, dataIndex: number, dataKeyIndex: number, detectChanges: boolean, isLatest: boolean) { this.listener.dataUpdated(data, this.listener.configDatasourceIndex, - dataIndex, dataKeyIndex, detectChanges); + dataIndex, dataKeyIndex, detectChanges, isLatest); } private processEntityData(entityData: EntityData, dataIndex: number, isUpdate: boolean, dataUpdatedCb: DataUpdatedCb) { - if (this.entityDataSubscriptionOptions.type === widgetType.latest && entityData.latest) { + if ((this.entityDataSubscriptionOptions.type === widgetType.latest || + this.entityDataSubscriptionOptions.type === widgetType.timeseries) && entityData.latest) { for (const type of Object.keys(entityData.latest)) { const subscriptionData = this.toSubscriptionData(entityData.latest[type], false); const dataKeyType = entityKeyTypeToDataKeyType(EntityKeyType[type]); - this.onData(subscriptionData, dataKeyType, dataIndex, true, dataUpdatedCb); + this.onData(subscriptionData, dataKeyType, dataIndex, true, + this.entityDataSubscriptionOptions.type === widgetType.timeseries, dataUpdatedCb); } } if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && entityData.timeseries) { @@ -660,7 +692,7 @@ export class EntityDataSubscription { if (!isUpdate) { prevDataCb = dataAggregator.updateOnDataCb((data, detectChanges) => { this.onData(data, this.datasourceType === DatasourceType.function ? - DataKeyType.function : DataKeyType.timeseries, dataIndex, detectChanges, dataUpdatedCb); + DataKeyType.function : DataKeyType.timeseries, dataIndex, detectChanges, false, dataUpdatedCb); }); } dataAggregator.onData({data: subscriptionData}, false, this.history, true); @@ -668,16 +700,16 @@ export class EntityDataSubscription { dataAggregator.updateOnDataCb(prevDataCb); } } else if (!this.history && !isUpdate) { - this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, dataUpdatedCb); + this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, false, dataUpdatedCb); } } } private onData(sourceData: SubscriptionData, type: DataKeyType, dataIndex: number, detectChanges: boolean, - dataUpdatedCb: DataUpdatedCb) { + isTsLatest: boolean, dataUpdatedCb: DataUpdatedCb) { for (const keyName of Object.keys(sourceData)) { const keyData = sourceData[keyName]; - const key = `${keyName}_${type}`; + const key = `${keyName}_${type}${isTsLatest ? '_latest' : ''}`; const dataKeyList = this.dataKeys[key] as Array; for (let keyIndex = 0; dataKeyList && keyIndex < dataKeyList.length; keyIndex++) { const datasourceKey = `${key}_${keyIndex}`; @@ -689,7 +721,7 @@ export class EntityDataSubscription { let datasourceKeyData: DataSet; let datasourceOrigKeyData: DataSet; let update = false; - if (this.realtime) { + if (this.realtime && !isTsLatest) { datasourceKeyData = []; datasourceOrigKeyData = []; } else { @@ -704,7 +736,7 @@ export class EntityDataSubscription { prevOrigSeries = [0, 0]; } this.datasourceOrigData[dataIndex][datasourceKey].data = []; - if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { + if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && !isTsLatest) { keyData.forEach((keySeries) => { let series = keySeries; const time = series[0]; @@ -719,7 +751,7 @@ export class EntityDataSubscription { prevSeries = series; }); update = true; - } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { + } else if (this.entityDataSubscriptionOptions.type === widgetType.latest || isTsLatest) { if (keyData.length > 0) { let series = keyData[0]; const time = series[0]; @@ -735,7 +767,7 @@ export class EntityDataSubscription { } if (update) { this.datasourceData[dataIndex][datasourceKey].data = data; - dataUpdatedCb(this.datasourceData[dataIndex][datasourceKey], dataIndex, dataKey.index, detectChanges); + dataUpdatedCb(this.datasourceData[dataIndex][datasourceKey], dataIndex, dataKey.index, detectChanges, isTsLatest); } } } @@ -774,7 +806,7 @@ export class EntityDataSubscription { dataUpdatedCb: DataUpdatedCb): DataAggregator { return new DataAggregator( (data, detectChanges) => { - this.onData(data, dataKeyType, dataIndex, detectChanges, dataUpdatedCb); + this.onData(data, dataKeyType, dataIndex, detectChanges, false, dataUpdatedCb); }, tsKeyNames, subsTw, @@ -783,17 +815,17 @@ export class EntityDataSubscription { ); } - private generateSeries(dataKey: SubscriptionDataKey, index: number, startTime: number, endTime: number): [number, any][] { + private generateSeries(dataKey: SubscriptionDataKey, startTime: number, endTime: number): [number, any][] { const data: [number, any][] = []; let prevSeries: [number, any]; - const datasourceDataKey = `${dataKey.key}_${index}`; + const datasourceDataKey = `${dataKey.key}_${dataKey.listIndex}`; const datasourceKeyData = this.datasourceData[0][datasourceDataKey].data; if (datasourceKeyData.length > 0) { prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; } else { prevSeries = [0, 0]; } - for (let time = startTime; time <= endTime && (this.timer || this.history); time += this.frequency) { + for (let time = startTime; time <= endTime && (this.timeseriesTimer || this.history); time += this.frequency) { const value = dataKey.func(time, prevSeries[1]); const series: [number, any] = [time, value]; data.push(series); @@ -807,7 +839,8 @@ export class EntityDataSubscription { private generateLatest(dataKey: SubscriptionDataKey, detectChanges: boolean) { let prevSeries: [number, any]; - const datasourceKeyData = this.datasourceData[0][dataKey.key].data; + const datasourceKey = dataKey.latest ? `${dataKey.key}_${dataKey.listIndex}` : dataKey.key; + const datasourceKeyData = this.datasourceData[0][datasourceKey].data; if (datasourceKeyData.length > 0) { prevSeries = datasourceKeyData[datasourceKeyData.length - 1]; } else { @@ -816,78 +849,100 @@ export class EntityDataSubscription { const time = Date.now() + this.latestTsOffset; const value = dataKey.func(time, prevSeries[1]); const series: [number, any] = [time, value]; - this.datasourceData[0][dataKey.key].data = [series]; - this.listener.dataUpdated(this.datasourceData[0][dataKey.key], + this.datasourceData[0][datasourceKey].data = [series]; + this.listener.dataUpdated(this.datasourceData[0][datasourceKey], this.listener.configDatasourceIndex, 0, - dataKey.index, detectChanges); + dataKey.index, detectChanges, dataKey.latest); } - private onTick(detectChanges: boolean) { - const now = this.utils.currentPerfTime(); - this.tickElapsed += now - this.tickScheduledTime; - this.tickScheduledTime = now; - - if (this.timer) { - clearTimeout(this.timer); - } + private generateData(detectChanges: boolean) { let key: string; + let tsDataKeys: SubscriptionDataKey[] = []; + let latestDataKeys: SubscriptionDataKey[] = []; if (this.entityDataSubscriptionOptions.type === widgetType.timeseries) { - let startTime: number; - let endTime: number; - let delta: number; - const generatedData: SubscriptionDataHolder = { - data: {} - }; - if (!this.history) { - delta = Math.floor(this.tickElapsed / this.frequency); - } - const deltaElapsed = this.history ? this.frequency : delta * this.frequency; - this.tickElapsed = this.tickElapsed - deltaElapsed; for (key of Object.keys(this.dataKeys)) { const dataKeyList = this.dataKeys[key] as Array; - for (let index = 0; index < dataKeyList.length && (this.timer || this.history); index ++) { - const dataKey = dataKeyList[index]; - if (!startTime) { - if (this.realtime) { - if (dataKey.lastUpdateTime) { - startTime = dataKey.lastUpdateTime + this.frequency; - endTime = dataKey.lastUpdateTime + deltaElapsed; - } else { - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs + - this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; - endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; - if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { - const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; - startTime = Math.max(time, startTime); - } - } - } else { - startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs + - this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; - endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs + - this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; - } - if (this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval) { - const currentTime = getCurrentTime().valueOf() + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; - endTime = Math.min(currentTime, endTime); - } - } - generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, index, startTime, endTime); - } - } - if (this.dataAggregators && this.dataAggregators.length) { - this.dataAggregators[0].onData(generatedData, true, this.history, detectChanges); + tsDataKeys = tsDataKeys.concat(dataKeyList.filter(dataKey => !dataKey.latest)); + latestDataKeys = latestDataKeys.concat(dataKeyList.filter(dataKey => dataKey.latest)); } } else if (this.entityDataSubscriptionOptions.type === widgetType.latest) { for (key of Object.keys(this.dataKeys)) { - this.generateLatest(this.dataKeys[key] as SubscriptionDataKey, detectChanges); + latestDataKeys.push(this.dataKeys[key] as SubscriptionDataKey); + } + } + if (tsDataKeys.length) { + this.timeseriesTimer = setTimeout(this.onTimeseriesTick.bind(this, tsDataKeys, true), 0); + } + if (latestDataKeys.length) { + this.onLatestTick(latestDataKeys, detectChanges); + } + } + + private onTimeseriesTick(tsDataKeys: SubscriptionDataKey[], detectChanges: boolean) { + const now = this.utils.currentPerfTime(); + this.tickElapsed += now - this.tickScheduledTime; + this.tickScheduledTime = now; + if (this.timeseriesTimer) { + clearTimeout(this.timeseriesTimer); + } + let startTime: number; + let endTime: number; + let delta: number; + const generatedData: SubscriptionDataHolder = { + data: {} + }; + if (!this.history) { + delta = Math.floor(this.tickElapsed / this.frequency); + } + const deltaElapsed = this.history ? this.frequency : delta * this.frequency; + this.tickElapsed = this.tickElapsed - deltaElapsed; + for (let index = 0; index < tsDataKeys.length && (this.timeseriesTimer || this.history); index ++) { + const dataKey = tsDataKeys[index]; + if (!startTime) { + if (this.realtime) { + if (dataKey.lastUpdateTime) { + startTime = dataKey.lastUpdateTime + this.frequency; + endTime = dataKey.lastUpdateTime + deltaElapsed; + } else { + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.startTs + + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + endTime = startTime + this.entityDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs + this.frequency; + if (this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.type === AggregationType.NONE) { + const time = endTime - this.frequency * this.entityDataSubscriptionOptions.subscriptionTimewindow.aggregation.limit; + startTime = Math.max(time, startTime); + } + } + } else { + startTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.startTimeMs + + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + endTime = this.entityDataSubscriptionOptions.subscriptionTimewindow.fixedWindow.endTimeMs + + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + } + if (this.entityDataSubscriptionOptions.subscriptionTimewindow.quickInterval) { + const currentTime = getCurrentTime().valueOf() + this.entityDataSubscriptionOptions.subscriptionTimewindow.tsOffset; + endTime = Math.min(currentTime, endTime); + } } + generatedData.data[`${dataKey.name}_${dataKey.index}`] = this.generateSeries(dataKey, startTime, endTime); + } + if (this.dataAggregators && this.dataAggregators.length) { + this.dataAggregators[0].onData(generatedData, true, this.history, detectChanges); } if (!this.history) { - this.timer = setTimeout(this.onTick.bind(this, true), this.frequency); + this.timeseriesTimer = setTimeout(this.onTimeseriesTick.bind(this, tsDataKeys, true), this.frequency); + } + } + + private onLatestTick(latestDataKeys: SubscriptionDataKey[], detectChanges: boolean) { + if (this.latestTimer) { + clearTimeout(this.latestTimer); } + latestDataKeys.forEach(dataKey => { + this.generateLatest(dataKey, detectChanges); + }); + this.latestTimer = setTimeout(this.onLatestTick.bind(this, latestDataKeys, true), this.latestFrequency); } } diff --git a/ui-ngx/src/app/core/api/entity-data.service.ts b/ui-ngx/src/app/core/api/entity-data.service.ts index 69e84e37b6..3d3112f007 100644 --- a/ui-ngx/src/app/core/api/entity-data.service.ts +++ b/ui-ngx/src/app/core/api/entity-data.service.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { DataSetHolder, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; +import { DataKey, DataSetHolder, Datasource, DatasourceType, widgetType } from '@shared/models/widget.models'; import { SubscriptionTimewindow } from '@shared/models/time/time.models'; import { EntityData, EntityDataPageLink, KeyFilter } from '@shared/models/query/query.models'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; @@ -38,7 +38,8 @@ export interface EntityDataListener { dataLoaded: (pageData: PageData, data: Array>, datasourceIndex: number, pageLink: EntityDataPageLink) => void; - dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; + dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, + detectChanges: boolean, isLatest: boolean) => void; initialPageDataChanged?: (nextPageData: PageData) => void; forceReInit?: () => void; updateRealtimeSubscription?: () => SubscriptionTimewindow; @@ -94,6 +95,7 @@ export class EntityDataService { if (listener.subscription) { if (listener.subscriptionType === widgetType.timeseries) { listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); + listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } else if (listener.subscriptionType === widgetType.latest) { listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } @@ -122,6 +124,7 @@ export class EntityDataService { listener.subscription = new EntityDataSubscription(listener, this.telemetryService, this.utils); if (listener.subscriptionType === widgetType.timeseries) { listener.subscriptionOptions.subscriptionTimewindow = deepClone(listener.subscriptionTimewindow); + listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } else if (listener.subscriptionType === widgetType.latest) { listener.subscriptionOptions.latestTsOffset = listener.latestTsOffset; } @@ -143,14 +146,13 @@ export class EntityDataService { ignoreDataUpdateOnIntervalTick: boolean): EntityDataSubscriptionOptions { const subscriptionDataKeys: Array = []; datasource.dataKeys.forEach((dataKey) => { - const subscriptionDataKey: SubscriptionDataKey = { - name: dataKey.name, - type: dataKey.type, - funcBody: dataKey.funcBody, - postFuncBody: dataKey.postFuncBody - }; - subscriptionDataKeys.push(subscriptionDataKey); + subscriptionDataKeys.push(this.toSubscriptionDataKey(dataKey, false)); }); + if (datasource.latestDataKeys) { + datasource.latestDataKeys.forEach((dataKey) => { + subscriptionDataKeys.push(this.toSubscriptionDataKey(dataKey, true)); + }); + } const entityDataSubscriptionOptions: EntityDataSubscriptionOptions = { datasourceType: datasource.type, dataKeys: subscriptionDataKeys, @@ -169,4 +171,14 @@ export class EntityDataService { entityDataSubscriptionOptions.ignoreDataUpdateOnIntervalTick = ignoreDataUpdateOnIntervalTick; return entityDataSubscriptionOptions; } + + private toSubscriptionDataKey(dataKey: DataKey, latest: boolean): SubscriptionDataKey { + return { + name: dataKey.name, + type: dataKey.type, + funcBody: dataKey.funcBody, + postFuncBody: dataKey.postFuncBody, + latest + }; + } } 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 fcb52a0071..6ae258663c 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -219,7 +219,9 @@ export interface SubscriptionMessage { export interface WidgetSubscriptionCallbacks { onDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; + onLatestDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; + onLatestDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; onSubscriptionMessage?: (subscription: IWidgetSubscription, message: SubscriptionMessage) => void; onInitialPageDataChanged?: (subscription: IWidgetSubscription, nextPageData: PageData) => void; forceReInit?: () => void; @@ -282,6 +284,7 @@ export interface IWidgetSubscription { dataPages?: PageData>[]; datasources?: Array; data?: Array; + latestData?: Array; hiddenData?: Array<{data: DataSet}>; timeWindowConfig?: Timewindow; timeWindow?: WidgetTimewindow; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index 8cbcd19918..bdbddffdcb 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -52,7 +52,14 @@ import { 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'; +import { + createLabelFromDatasource, createLabelFromPattern, + deepClone, flatFormattedData, + formattedDataFormDatasourceData, + isDefined, + isDefinedAndNotNull, + isEqual +} from '@core/utils'; import { EntityId } from '@app/shared/models/id/entity-id'; import * as moment_ from 'moment'; import { emptyPageData, PageData } from '@shared/models/page/page-data'; @@ -99,6 +106,7 @@ export class WidgetSubscription implements IWidgetSubscription { configuredDatasources: Array; data: Array; + latestData: Array; datasources: Array; hiddenData: Array; legendData: LegendData; @@ -140,6 +148,7 @@ export class WidgetSubscription implements IWidgetSubscription { executingSubjects: Array>; subscribed = false; + hasLatestData = false; widgetTimewindowChangedSubject: Subject = new ReplaySubject(); widgetTimewindowChanged$ = this.widgetTimewindowChangedSubject.asObservable().pipe( @@ -205,7 +214,9 @@ export class WidgetSubscription implements IWidgetSubscription { }); } else { this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || (() => {}); + this.callbacks.onLatestDataUpdated = this.callbacks.onLatestDataUpdated || (() => {}); this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); + this.callbacks.onLatestDataUpdateError = this.callbacks.onLatestDataUpdateError || (() => {}); this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {}); this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {}); this.callbacks.forceReInit = this.callbacks.forceReInit || (() => {}); @@ -224,6 +235,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.datasources = []; this.dataPages = []; this.data = []; + this.latestData = []; this.hiddenData = []; this.originalTimewindow = null; this.timeWindow = {}; @@ -366,6 +378,7 @@ export class WidgetSubscription implements IWidgetSubscription { const initDataSubscriptionSubject = new ReplaySubject(1); this.loadStDiff().subscribe(() => { if (!this.ctx.aliasController) { + this.configuredDatasources = deepClone(this.configuredDatasources); this.hasResolvedData = true; this.prepareDataSubscriptions().subscribe( () => { @@ -455,18 +468,25 @@ export class WidgetSubscription implements IWidgetSubscription { this.updateDataTimewindow(); this.notifyDataLoaded(); this.onDataUpdated(true); + if (this.hasLatestData) { + this.onLatestDataUpdated(true); + } }) ); } private resetData() { this.data.length = 0; + this.latestData.length = 0; this.hiddenData.length = 0; if (this.displayLegend) { this.legendData.keys.length = 0; this.legendData.data.length = 0; } this.onDataUpdated(); + if (this.hasLatestData) { + this.onLatestDataUpdated(); + } } getFirstEntityInfo(): SubscriptionEntityInfo { @@ -576,6 +596,20 @@ export class WidgetSubscription implements IWidgetSubscription { }); } + private onLatestDataUpdated(detectChanges?: boolean) { + if (this.cafs.latestDataUpdated) { + this.cafs.latestDataUpdated(); + this.cafs.latestDataUpdated = null; + } + this.cafs.latestDataUpdated = this.ctx.raf.raf(() => { + try { + this.callbacks.onLatestDataUpdated(this, detectChanges); + } catch (e) { + this.callbacks.onLatestDataUpdateError(this, e); + } + }); + } + private onSubscriptionMessage(message: SubscriptionMessage) { if (this.cafs.message) { this.cafs.message(); @@ -1090,6 +1124,7 @@ export class WidgetSubscription implements IWidgetSubscription { private updateDataSubscriptions() { this.configuredDatasources = this.ctx.utils.validateDatasources(this.options.datasources); if (!this.ctx.aliasController) { + this.configuredDatasources = deepClone(this.configuredDatasources); this.hasResolvedData = true; this.prepareDataSubscriptions().subscribe( () => { @@ -1249,21 +1284,28 @@ export class WidgetSubscription implements IWidgetSubscription { if (isUpdate) { this.configureLoadedData(); this.onDataUpdated(true); + if (this.hasLatestData) { + this.onLatestDataUpdated(true); + } } } private configureLoadedData() { this.datasources.length = 0; this.data.length = 0; + this.latestData.length = 0; this.hiddenData.length = 0; + this.hasLatestData = false; if (this.displayLegend) { this.legendData.keys.length = 0; this.legendData.data.length = 0; } let dataKeyIndex = 0; + let latestDataKeyIndex = 0; this.configuredDatasources.forEach((configuredDatasource, datasourceIndex) => { configuredDatasource.dataKeyStartIndex = dataKeyIndex; + configuredDatasource.latestDataKeyStartIndex = latestDataKeyIndex; const datasourcesPage = this.datasourcePages[datasourceIndex]; const datasourceDataPage = this.dataPages[datasourceIndex]; if (datasourcesPage) { @@ -1289,6 +1331,15 @@ export class WidgetSubscription implements IWidgetSubscription { } dataKeyIndex++; }); + if (datasource.latestDataKeys && datasource.latestDataKeys.length) { + this.hasLatestData = true; + datasource.latestDataKeys.forEach((dataKey, currentLatestDataKeyIndex) => { + const currentDataKeyIndex = datasource.dataKeys.length + currentLatestDataKeyIndex; + const datasourceData = datasourceDataPage.data[currentDatasourceIndex][currentDataKeyIndex]; + this.latestData.push(datasourceData); + latestDataKeyIndex++; + }); + } this.datasources.push(datasource); }); } @@ -1303,6 +1354,15 @@ export class WidgetSubscription implements IWidgetSubscription { } index++; }); + if (datasource.latestDataKeys) { + datasource.latestDataKeys.forEach((dataKey) => { + if (datasource.generated || datasource.isAdditional) { + dataKey._hash = Math.random(); + // dataKey.color = this.ctx.utils.getMaterialColor(index); + } + // index++; + }); + } }); if (this.comparisonEnabled) { this.datasourcePages.forEach(datasourcePage => { @@ -1329,19 +1389,11 @@ export class WidgetSubscription implements IWidgetSubscription { } private entityDataToDatasourceData(datasource: Datasource, data: Array): Array { - return datasource.dataKeys.map((dataKey, keyIndex) => { + let datasourceDataArray: Array = []; + datasourceDataArray = datasourceDataArray.concat(datasource.dataKeys.map((dataKey, keyIndex) => { dataKey.hidden = !!dataKey.settings.hideDataByDefault; dataKey.inLegend = !dataKey.settings.removeFromLegend; dataKey.label = this.ctx.utils.customTranslation(dataKey.label, dataKey.label); - if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) { - dataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); - } else { - if (this.comparisonEnabled && dataKey.isAdditional) { - dataKey.label = dataKey.label + ' ' + this.ctx.translate.instant('legend.comparison-time-ago.' + this.timeForComparison); - } - dataKey.pattern = dataKey.label; - dataKey.label = createLabelFromDatasource(datasource, dataKey.pattern); - } const datasourceData: DatasourceData = { datasource, dataKey, @@ -1351,7 +1403,38 @@ export class WidgetSubscription implements IWidgetSubscription { datasourceData.data = data[keyIndex].data; } return datasourceData; + })); + if (datasource.latestDataKeys) { + datasourceDataArray = datasourceDataArray.concat(datasource.latestDataKeys.map((dataKey, latestKeyIndex) => { + const datasourceData: DatasourceData = { + datasource, + dataKey, + data: [] + }; + const keyIndex = datasource.dataKeys.length + latestKeyIndex; + if (data && data[keyIndex] && data[keyIndex].data) { + datasourceData.data = data[keyIndex].data; + } + return datasourceData; + })); + } + + const formattedDataArray = formattedDataFormDatasourceData(datasourceDataArray); + const formattedData = flatFormattedData(formattedDataArray); + + datasource.dataKeys.forEach((dataKey) => { + if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) { + dataKey.label = createLabelFromPattern(dataKey.settings.comparisonSettings.comparisonValuesLabel, formattedData); + } else { + if (this.comparisonEnabled && dataKey.isAdditional) { + dataKey.label = dataKey.label + ' ' + this.ctx.translate.instant('legend.comparison-time-ago.' + this.timeForComparison); + } + dataKey.pattern = dataKey.label; + dataKey.label = createLabelFromPattern(dataKey.pattern, formattedData); + } }); + + return datasourceDataArray; } private entityDataToDatasource(configDatasource: Datasource, entityData: EntityData, index: number): Datasource { @@ -1362,7 +1445,17 @@ export class WidgetSubscription implements IWidgetSubscription { return newDatasource; } - private dataUpdated(data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) { + private dataUpdated(data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, + detectChanges: boolean, isLatest: boolean) { + if (isLatest) { + this.processLatestDataUpdated(data, datasourceIndex, dataIndex, dataKeyIndex, detectChanges); + } else { + this.processDataUpdated(data, datasourceIndex, dataIndex, dataKeyIndex, detectChanges); + } + } + + private processDataUpdated(data: DataSetHolder, datasourceIndex: number, dataIndex: number, + dataKeyIndex: number, detectChanges: boolean) { const configuredDatasource = this.configuredDatasources[datasourceIndex]; const startIndex = configuredDatasource.dataKeyStartIndex; const dataKeysCount = configuredDatasource.dataKeys.length; @@ -1402,6 +1495,30 @@ export class WidgetSubscription implements IWidgetSubscription { } } + private processLatestDataUpdated(data: DataSetHolder, datasourceIndex: number, dataIndex: number, + dataKeyIndex: number, detectChanges: boolean) { + const configuredDatasource = this.configuredDatasources[datasourceIndex]; + const startIndex = configuredDatasource.latestDataKeyStartIndex; + const dataKeysCount = configuredDatasource.latestDataKeys.length; + const index = startIndex + dataIndex * dataKeysCount + dataKeyIndex - configuredDatasource.dataKeys.length; + let update = true; + const currentData = this.latestData[index]; + const prevData = currentData.data; + if (!data.data.length) { + update = false; + } else if (prevData && prevData[0] && prevData[0].length > 1 && data.data.length > 0) { + const prevTs = prevData[0][0]; + const prevValue = prevData[0][1]; + if (prevTs === data.data[0][0] && prevValue === data.data[0][1]) { + update = false; + } + } + if (update) { + currentData.data = data.data; + this.onLatestDataUpdated(detectChanges); + } + } + private alarmsLoaded(alarms: PageData, allowedEntities: number, totalEntities: number) { this.alarms = alarms; if (totalEntities > allowedEntities) { diff --git a/ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts b/ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts index e9ce449b1f..a2f70ca7bd 100644 --- a/ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts +++ b/ui-ngx/src/app/core/guards/confirm-on-exit.guard.ts @@ -21,7 +21,7 @@ import { select, Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { AuthState } from '@core/auth/auth.models'; import { selectAuth } from '@core/auth/auth.selectors'; -import { take } from 'rxjs/operators'; +import { map, take } from 'rxjs/operators'; import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { isDefined } from '../utils'; @@ -69,6 +69,17 @@ export class ConfirmOnExitGuard implements CanDeactivate { + if (dialogResult) { + if (component.confirmForm && component.confirmForm()) { + component.confirmForm().markAsPristine(); + } else { + component.isDirty = false; + } + } + return dialogResult; + }) ); } } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 2f4f564325..e6f2e20adc 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -17,7 +17,7 @@ import _ from 'lodash'; import { Observable, Subject } from 'rxjs'; import { finalize, share } from 'rxjs/operators'; -import { Datasource } from '@app/shared/models/widget.models'; +import { Datasource, DatasourceData, FormattedData, ReplaceInfo } from '@app/shared/models/widget.models'; import { EntityId } from '@shared/models/id/entity-id'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { EntityType, baseDetailsPageByEntityType } from '@shared/models/entity-type.models'; @@ -388,6 +388,195 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin return label; } +export function formattedDataFormDatasourceData(input: DatasourceData[], dataIndex?: number): FormattedData[] { + return _(input).groupBy(el => el.datasource.entityName + el.datasource.entityType) + .values().value().map((entityArray, i) => { + const datasource = entityArray[0].datasource; + const obj = formattedDataFromDatasource(datasource, i); + entityArray.filter(el => el.data.length).forEach(el => { + const index = isDefined(dataIndex) ? dataIndex : el.data.length - 1; + if (!obj.hasOwnProperty(el.dataKey.label) || el.data[index][1] !== '') { + obj[el.dataKey.label] = el.data[index][1]; + obj[el.dataKey.label + '|ts'] = el.data[index][0]; + if (el.dataKey.label.toLowerCase() === 'type') { + obj.deviceType = el.data[index][1]; + } + } + }); + return obj; + }); +} + +export function formattedDataArrayFromDatasourceData(input: DatasourceData[]): FormattedData[][] { + return _(input).groupBy(el => el.datasource.entityName) + .values().value().map((entityArray, dsIndex) => { + const timeDataMap: {[time: number]: FormattedData} = {}; + entityArray.filter(e => e.data.length).forEach(entity => { + entity.data.forEach(tsData => { + const time = tsData[0]; + const value = tsData[1]; + let data = timeDataMap[time]; + if (!data) { + const datasource = entity.datasource; + data = formattedDataFromDatasource(datasource, dsIndex); + data.time = time; + timeDataMap[time] = data; + } + data[entity.dataKey.label] = value; + data[entity.dataKey.label + '|ts'] = time; + if (entity.dataKey.label.toLowerCase() === 'type') { + data.deviceType = value; + } + }); + }); + return _.values(timeDataMap); + }); +} + +export function formattedDataFromDatasource(datasource: Datasource, dsIndex: number): FormattedData { + return { + entityName: datasource.entityName, + deviceName: datasource.entityName, + entityId: datasource.entityId, + entityType: datasource.entityType, + entityLabel: datasource.entityLabel || datasource.entityName, + entityDescription: datasource.entityDescription, + aliasName: datasource.aliasName, + $datasource: datasource, + dsIndex, + dsName: datasource.name, + deviceType: null + }; +} + +export function flatFormattedData(input: FormattedData[]): FormattedData { + let result: FormattedData = {} as FormattedData; + if (input.length) { + for (const toMerge of input) { + result = {...result, ...toMerge}; + } + const sourceData = input[0]; + result.entityName = sourceData.entityName; + result.deviceName = sourceData.deviceName; + result.entityId = sourceData.entityId; + result.entityType = sourceData.entityType; + result.entityLabel = sourceData.entityLabel; + result.entityDescription = sourceData.entityDescription; + result.aliasName = sourceData.aliasName; + result.$datasource = sourceData.$datasource; + result.dsIndex = sourceData.dsIndex; + result.dsName = sourceData.dsName; + result.deviceType = sourceData.deviceType; + } + return result; +} + +export function mergeFormattedData(first: FormattedData[], second: FormattedData[]): FormattedData[] { + const merged = first.concat(second); + return _(merged).groupBy(el => el.$datasource) + .values().value().map((formattedDataArray, i) => { + let res = formattedDataArray[0]; + if (formattedDataArray.length > 1) { + const toMerge = formattedDataArray[1]; + res = {...res, ...toMerge}; + } + return res; + }); +} + +export function processDataPattern(pattern: string, data: FormattedData): Array { + const replaceInfo: Array = []; + try { + const reg = /\${([^}]*)}/g; + let match = reg.exec(pattern); + while (match !== null) { + const variableInfo: ReplaceInfo = { + dataKeyName: '', + valDec: 2, + variable: '' + }; + const variable = match[0]; + let label = match[1]; + let valDec = 2; + const splitValues = label.split(':'); + if (splitValues.length > 1) { + label = splitValues[0]; + valDec = parseFloat(splitValues[1]); + } + + variableInfo.variable = variable; + variableInfo.valDec = valDec; + + if (label.startsWith('#')) { + const keyIndexStr = label.substring(1); + const n = Math.floor(Number(keyIndexStr)); + if (String(n) === keyIndexStr && n >= 0) { + variableInfo.dataKeyName = data.$datasource.dataKeys[n].label; + } + } else { + variableInfo.dataKeyName = label; + } + replaceInfo.push(variableInfo); + + match = reg.exec(pattern); + } + } catch (ex) { + console.log(ex, pattern); + } + return replaceInfo; +} + +export function fillDataPattern(pattern: string, replaceInfo: Array, data: FormattedData) { + let text = createLabelFromDatasource(data.$datasource, pattern); + if (replaceInfo) { + for (const variableInfo of replaceInfo) { + let txtVal = ''; + if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) { + const varData = data[variableInfo.dataKeyName]; + if (isNumber(varData)) { + txtVal = padValue(varData, variableInfo.valDec); + } else { + txtVal = varData; + } + } + text = text.replace(variableInfo.variable, txtVal); + } + } + return text; +} + +export function createLabelFromPattern(pattern: string, data: FormattedData): string { + const replaceInfo = processDataPattern(pattern, data); + return fillDataPattern(pattern, replaceInfo, data); +} + +export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any { + let res = null; + if (source?.length) { + try { + res = new Function(...params, source); + } + catch (err) { + res = null; + } + } + return res; +} + +export function safeExecute(func: (...args: any[]) => any, params = []) { + let res = null; + if (func && typeof (func) === 'function') { + try { + res = func(...params); + } + catch (err) { + console.log('error in external function:', err); + res = null; + } + } + return res; +} + export function padValue(val: any, dec: number): string { let strVal; let n; diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts index da09828194..cb3d24ba79 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-alias-dialog.component.ts @@ -141,21 +141,23 @@ export class EntityAliasDialogComponent extends DialogComponent { - if (this.isAdd) { - this.alias.id = this.utils.guid(); + if (this.alias.filter.type) { + this.validate().subscribe(() => { + if (this.isAdd) { + this.alias.id = this.utils.guid(); + } + this.dialogRef.close(this.alias); + }, + () => { + this.entityAliasFormGroup.setErrors({ + noEntityMatched: true + }); + const changesSubscriptuion = this.entityAliasFormGroup.valueChanges.subscribe(() => { + this.entityAliasFormGroup.setErrors(null); + changesSubscriptuion.unsubscribe(); + }); } - this.dialogRef.close(this.alias); - }, - () => { - this.entityAliasFormGroup.setErrors({ - noEntityMatched: true - }); - const changesSubscriptuion = this.entityAliasFormGroup.valueChanges.subscribe(() => { - this.entityAliasFormGroup.setErrors(null); - changesSubscriptuion.unsubscribe(); - }); - } - ); + ); + } } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts index 8e8745dd20..5ba635a9ae 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts @@ -68,6 +68,7 @@ export class AddWidgetDialogComponent extends DialogComponent { return this.openImportDialog('rulechain.import', 'rulechain.rulechain-file').pipe( - map((ruleChainImport: RuleChainImport) => { + mergeMap((ruleChainImport: RuleChainImport) => { if (!this.validateImportedRuleChain(ruleChainImport)) { this.store.dispatch(new ActionNotificationShow( {message: this.translate.instant('rulechain.invalid-rulechain-file-error'), @@ -435,7 +436,7 @@ export class ImportExportService { type: 'error'})); throw new Error('Invalid rule chain type'); } else { - return ruleChainImport; + return this.processOldRuleChainConnections(ruleChainImport); } }), catchError((err) => { @@ -444,6 +445,53 @@ export class ImportExportService { ); } + private processOldRuleChainConnections(ruleChainImport: RuleChainImport): Observable { + const metadata = ruleChainImport.metadata; + if ((metadata as any).ruleChainConnections) { + const ruleChainNameResolveObservables: Observable[] = []; + for (const ruleChainConnection of (metadata as any).ruleChainConnections) { + if (ruleChainConnection.targetRuleChainId && ruleChainConnection.targetRuleChainId.id) { + const ruleChainNode: RuleNode = { + name: '', + debugMode: false, + type: 'org.thingsboard.rule.engine.flow.TbRuleChainInputNode', + configuration: { + ruleChainId: ruleChainConnection.targetRuleChainId.id + }, + additionalInfo: ruleChainConnection.additionalInfo + }; + ruleChainNameResolveObservables.push(this.ruleChainService.getRuleChain(ruleChainNode.configuration.ruleChainId, + {ignoreErrors: true, ignoreLoading: true}).pipe( + catchError(err => { + return of({name: 'Rule Chain Input'} as RuleChain); + }), + map((ruleChain => { + ruleChainNode.name = ruleChain.name; + return null; + }) + ) + )); + const toIndex = metadata.nodes.length; + metadata.nodes.push(ruleChainNode); + metadata.connections.push({ + toIndex, + fromIndex: ruleChainConnection.fromIndex, + type: ruleChainConnection.type + }); + } + } + if (ruleChainNameResolveObservables.length) { + return forkJoin(ruleChainNameResolveObservables).pipe( + map(() => ruleChainImport) + ); + } else { + return of(ruleChainImport); + } + } else { + return of(ruleChainImport); + } + } + public exportDeviceProfile(deviceProfileId: string) { this.deviceProfileService.getDeviceProfile(deviceProfileId).subscribe( (deviceProfile) => { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html index 70bac95f66..00ecd46582 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.html @@ -134,4 +134,10 @@ +
+ + {{ 'device-profile.mqtt-send-ack-on-validation-exception' | translate }} + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts index 6e0a1617e1..b51b42f1d2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/mqtt-device-profile-transport-configuration.component.ts @@ -92,6 +92,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control this.mqttDeviceProfileTransportConfigurationFormGroup = this.fb.group({ deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]], deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]], + sendAckOnValidationException: [false, Validators.required], transportPayloadTypeConfiguration: this.fb.group({ transportPayloadType: [TransportPayloadType.JSON, Validators.required], deviceTelemetryProtoSchema: [defaultTelemetrySchema, Validators.required], diff --git a/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.html new file mode 100644 index 0000000000..1d16aefb19 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.html @@ -0,0 +1,162 @@ + +
+
+ + admin.smpp-provider.smpp-version + + + {{smppVersion.value}} + + + + + admin.smpp-provider.smpp-host + + + {{'admin.smpp-provider.smpp-host-required' | translate}} + + + + admin.smpp-provider.smpp-port + + + {{'admin.smpp-provider.smpp-port-required' | translate}} + + +
+
+ + admin.smpp-provider.system-id + + + {{'admin.smpp-provider.system-id-required' | translate}} + + + + admin.smpp-provider.password + + + + {{'admin.smpp-provider.password-required' | translate}} + + +
+ + + + + admin.smpp-provider.type-settings + + + + + admin.smpp-provider.system-type + + + + admin.smpp-provider.bind-type + + + {{bindTypesTranslation.get(bindType) | translate}} + + + + + admin.smpp-provider.service-type + + + + + + + + admin.smpp-provider.source-settings + + + + + admin.smpp-provider.source-address + + + + admin.smpp-provider.source-ton + + + {{typeOfNumberMap.get(sourceTon).name | translate}} + + + + + admin.smpp-provider.source-npi + + + {{numberingPlanIdentificationMap.get(sourceNpi).name | translate}} + + + + + + + + + admin.smpp-provider.destination-settings + + + + + admin.smpp-provider.destination-ton + + + {{typeOfNumberMap.get(destinationTon).name | translate}} + + + + + admin.smpp-provider.destination-npi + + + {{numberingPlanIdentificationMap.get(destinationNpi).name | translate}} + + + + + + + + + admin.smpp-provider.additional-settings + + + + + admin.smpp-provider.address-range + + + + admin.smpp-provider.coding-scheme + + + {{codingSchemesMap.get(codingScheme).name | translate}} + + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.ts b/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.ts new file mode 100644 index 0000000000..605af2cb51 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/sms/smpp-sms-provider-configuration.component.ts @@ -0,0 +1,137 @@ +/// +/// Copyright © 2016-2022 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, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + AwsSnsSmsProviderConfiguration, + BindTypes, + bindTypesTranslationMap, + CodingSchemes, + codingSchemesMap, + NumberingPlanIdentification, + numberingPlanIdentificationMap, + SmppSmsProviderConfiguration, + smppVersions, + SmsProviderConfiguration, + SmsProviderType, + TypeOfNumber, + typeOfNumberMap +} from '@shared/models/settings.models'; +import { isDefinedAndNotNull } from '@core/utils'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; + +@Component({ + selector: 'tb-smpp-sms-provider-configuration', + templateUrl: './smpp-sms-provider-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SmppSmsProviderConfigurationComponent), + multi: true + }] +}) + +export class SmppSmsProviderConfigurationComponent implements ControlValueAccessor, OnInit{ + constructor(private fb: FormBuilder) { + } + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + @Input() + disabled: boolean; + + smppSmsProviderConfigurationFormGroup: FormGroup; + + smppVersions = smppVersions; + + bindTypes = Object.keys(BindTypes); + bindTypesTranslation = bindTypesTranslationMap; + + typeOfNumber = Object.keys(TypeOfNumber); + typeOfNumberMap = typeOfNumberMap; + + numberingPlanIdentification = Object.keys(NumberingPlanIdentification); + numberingPlanIdentificationMap = numberingPlanIdentificationMap; + + codingSchemes = Object.keys(CodingSchemes); + codingSchemesMap = codingSchemesMap; + + private propagateChange = (v: any) => { }; + + ngOnInit(): void { + this.smppSmsProviderConfigurationFormGroup = this.fb.group({ + protocolVersion: [null, [Validators.required]], + host: [null, [Validators.required]], + port: [null, [Validators.required]], + systemId: [null, [Validators.required]], + password: [null, [Validators.required]], + systemType: [null], + bindType: [null, []], + serviceType: [null, []], + sourceAddress: [null, []], + sourceTon: [null, []], + sourceNpi: [null, []], + destinationTon: [null, []], + destinationNpi: [null, []], + addressRange: [null, []], + codingScheme: [null, []], + }); + + this.smppSmsProviderConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateValue(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.smppSmsProviderConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.smppSmsProviderConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: AwsSnsSmsProviderConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.smppSmsProviderConfigurationFormGroup.patchValue(value, {emitEvent: false}); + } + } + + private updateValue() { + let configuration: SmppSmsProviderConfiguration = null; + if (this.smppSmsProviderConfigurationFormGroup.valid) { + configuration = this.smppSmsProviderConfigurationFormGroup.value; + (configuration as SmsProviderConfiguration).type = SmsProviderType.SMPP; + } + this.propagateChange(configuration); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html index a99c4ec029..f4d9d0d1c9 100644 --- a/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/sms/sms-provider-configuration.component.html @@ -40,5 +40,11 @@ formControlName="configuration"> + + + + diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html index 40d6deaa05..216927fe76 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.html @@ -73,7 +73,7 @@ close - DataKey; + generateDataKey: (chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema) => DataKey; fetchEntityKeys: (entityAliasId: string, types: Array) => Observable>; } diff --git a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts index 781c30b02f..9286c50cc8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/data-keys.component.ts @@ -46,7 +46,7 @@ import { MatAutocomplete } from '@angular/material/autocomplete'; import { MatChipInputEvent, MatChipList } from '@angular/material/chips'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { DataKey, DatasourceType, widgetType } from '@shared/models/widget.models'; +import { DataKey, DatasourceType, JsonSettingsSchema, widgetType } from '@shared/models/widget.models'; import { IAliasController } from '@core/api/widget-api.models'; import { DataKeysCallbacks } from './data-keys.component.models'; import { alarmFields } from '@shared/models/alarm.models'; @@ -111,7 +111,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie aliasController: IAliasController; @Input() - datakeySettingsSchema: any; + datakeySettingsSchema: JsonSettingsSchema; @Input() callbacks: DataKeysCallbacks; @@ -145,6 +145,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie dataKeyType: DataKeyType; placeholder: string; + secondaryPlaceholder: string; requiredText: string; searchText = ''; @@ -230,20 +231,34 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie private updateParams() { if (this.datasourceType === DatasourceType.function) { this.dataKeyType = DataKeyType.function; - this.placeholder = this.translate.instant('datakey.function-types'); this.requiredText = this.translate.instant('datakey.function-types-required'); + if (this.widgetType === widgetType.latest) { + this.placeholder = this.translate.instant('datakey.latest-key-functions'); + this.secondaryPlaceholder = '+' + this.translate.instant('datakey.latest-key-function'); + } else if (this.widgetType === widgetType.alarm) { + this.placeholder = this.translate.instant('datakey.alarm-key-functions'); + this.secondaryPlaceholder = '+' + this.translate.instant('alarm-key-function'); + } else { + this.placeholder = this.translate.instant('datakey.timeseries-key-functions'); + this.secondaryPlaceholder = '+' + this.translate.instant('datakey.timeseries-key-function'); + } } else { if (this.widgetType === widgetType.latest) { this.dataKeyType = null; + this.placeholder = this.translate.instant('datakey.latest-keys'); + this.secondaryPlaceholder = '+' + this.translate.instant('datakey.latest-key'); this.requiredText = this.translate.instant('datakey.timeseries-or-attributes-required'); } else if (this.widgetType === widgetType.alarm) { this.dataKeyType = null; + this.placeholder = this.translate.instant('datakey.alarm-keys'); + this.secondaryPlaceholder = '+' + this.translate.instant('datakey.alarm-key'); this.requiredText = this.translate.instant('datakey.alarm-fields-timeseries-or-attributes-required'); } else { this.dataKeyType = DataKeyType.timeseries; + this.placeholder = this.translate.instant('datakey.timeseries-keys'); + this.secondaryPlaceholder = '+' + this.translate.instant('datakey.timeseries-key'); this.requiredText = this.translate.instant('datakey.timeseries-required'); } - this.placeholder = ''; } } @@ -251,7 +266,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie if (this.widgetType === widgetType.alarm) { this.keys = this.utils.getDefaultAlarmDataKeys(); } else if (this.isEntityCountDatasource) { - this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count)]; + this.keys = [this.callbacks.generateDataKey('count', DataKeyType.count, this.datakeySettingsSchema)]; } else { this.keys = []; } @@ -325,7 +340,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, AfterVie } private addFromChipValue(chip: DataKey) { - const key = this.callbacks.generateDataKey(chip.name, chip.type); + const key = this.callbacks.generateDataKey(chip.name, chip.type, this.datakeySettingsSchema); this.addKey(key); } diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.html b/ui-ngx/src/app/modules/home/components/widget/legend.component.html index bbead89d6b..0b02d3389c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.html @@ -15,8 +15,7 @@ limitations under the License. --> - +
diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.scss b/ui-ngx/src/app/modules/home/components/widget/legend.component.scss index 00bcc37246..9aefd0c88d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.scss @@ -24,6 +24,7 @@ &.tb-legend-row { width: auto; margin-left: auto; + margin-right: auto; } .tb-legend-header, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts index 36026ab6ae..f790393dad 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.models.ts @@ -235,6 +235,12 @@ export interface TbFlotKeySettings { comparisonSettings: TbFlotKeyComparisonSettings; } +export interface TbFlotLatestKeySettings { + useAsThreshold: boolean; + thresholdLineWidth: number; + thresholdColor: string; +} + export function flotSettingsSchema(chartType: ChartType): JsonSettingsSchema { const schema: JsonSettingsSchema = { @@ -1124,3 +1130,37 @@ export function flotDatakeySettingsSchema(defaultShowLines: boolean, chartType: return schema; } + +export const flotLatestDatakeySettingsSchema: JsonSettingsSchema = { + schema: { + type: 'object', + title: 'LatestDataKeySettings', + properties: { + useAsThreshold: { + title: 'Use key value as threshold', + type: 'boolean', + default: false + }, + thresholdLineWidth: { + title: 'Threshold line width', + type: 'number' + }, + thresholdColor: { + title: 'Threshold color', + type: 'string' + } + } + }, + form: [ + 'useAsThreshold', + { + key: 'thresholdLineWidth', + condition: 'model.useAsThreshold === true' + }, + { + key: 'thresholdColor', + type: 'color', + condition: 'model.useAsThreshold === true' + }, + ] +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts index 70ac80534e..736cc30450 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/flot-widget.ts @@ -38,13 +38,13 @@ import { } from '@app/shared/models/widget.models'; import { ChartType, - flotDatakeySettingsSchema, + flotDatakeySettingsSchema, flotLatestDatakeySettingsSchema, flotPieDatakeySettingsSchema, flotPieSettingsSchema, flotSettingsSchema, TbFlotAxisOptions, TbFlotHoverInfo, - TbFlotKeySettings, + TbFlotKeySettings, TbFlotLatestKeySettings, TbFlotPlotAxis, TbFlotPlotDataSeries, TbFlotPlotItem, @@ -69,6 +69,7 @@ const moment = moment_; const flotPieSettingsSchemaValue = flotPieSettingsSchema; const flotPieDatakeySettingsSchemaValue = flotPieDatakeySettingsSchema; +const latestDatakeySettingsSchemaValue = flotLatestDatakeySettingsSchema; export class TbFlot { @@ -98,6 +99,8 @@ export class TbFlot { private thresholdsSourcesSubscription: IWidgetSubscription; private predefinedThresholds: TbFlotThresholdMarking[]; + private latestDataThresholds: TbFlotThresholdMarking[]; + private attributesThresholds: TbFlotThresholdMarking[]; private labelPatternsSourcesSubscription: IWidgetSubscription; private labelPatternsSourcesData: DatasourceData[]; @@ -107,6 +110,7 @@ export class TbFlot { private createPlotTimeoutHandle: Timeout; private updateTimeoutHandle: Timeout; + private latestUpdateTimeoutHandle: Timeout; private resizeTimeoutHandle: Timeout; private mouseEventsEnabled: boolean; @@ -145,6 +149,10 @@ export class TbFlot { return flotDatakeySettingsSchema(defaultShowLines, chartType); } + static latestDatakeySettingsSchema(): JsonSettingsSchema { + return latestDatakeySettingsSchemaValue; + } + constructor(private ctx: WidgetContext, private readonly chartType: ChartType) { this.chartType = this.chartType || 'line'; this.settings = ctx.settings as TbFlotSettings; @@ -541,7 +549,6 @@ export class TbFlot { } this.subscribeForThresholdsAttributes(thresholdsDatasources); - this.options.grid.markings = predefinedThresholds; this.predefinedThresholds = predefinedThresholds; this.options.colors = colors; @@ -561,6 +568,12 @@ export class TbFlot { this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; } + let allThresholds = deepClone(this.predefinedThresholds); + if (this.attributesThresholds) { + allThresholds = allThresholds.concat(this.attributesThresholds); + } + this.latestDataThresholds = this.thresholdsSourcesDataUpdated(allThresholds, this.subscription.latestData, true); + this.options.grid.markings = allThresholds.concat(this.latestDataThresholds); } this.checkMouseEvents(); @@ -591,7 +604,6 @@ export class TbFlot { if (this.subscription) { if (!this.isMouseInteraction && this.plot) { if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { - let axisVisibilityChanged = false; if (this.yaxis) { for (let i = 0; i < this.subscription.data.length; i++) { @@ -681,6 +693,31 @@ export class TbFlot { } } + public latestDataUpdate() { + if (this.latestUpdateTimeoutHandle) { + clearTimeout(this.latestUpdateTimeoutHandle); + this.latestUpdateTimeoutHandle = null; + } + if (this.subscription) { + if (!this.isMouseInteraction && this.plot) { + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { + let allThresholds = deepClone(this.predefinedThresholds); + if (this.attributesThresholds) { + allThresholds = allThresholds.concat(this.attributesThresholds); + } + this.latestDataThresholds = this.thresholdsSourcesDataUpdated(allThresholds, this.subscription.latestData, true); + this.options.grid.markings = allThresholds.concat(this.latestDataThresholds); + if (this.plot) { + this.plot.getOptions().grid.markings = this.options.grid.markings; + this.updateData(); + } + } + } else if (this.isMouseInteraction && this.plot) { + this.latestUpdateTimeoutHandle = setTimeout(this.latestDataUpdate.bind(this), 30); + } + } + } + public resize() { if (this.resizeTimeoutHandle) { clearTimeout(this.resizeTimeoutHandle); @@ -734,6 +771,10 @@ export class TbFlot { clearTimeout(this.updateTimeoutHandle); this.updateTimeoutHandle = null; } + if (this.latestUpdateTimeoutHandle) { + clearTimeout(this.latestUpdateTimeoutHandle); + this.latestUpdateTimeoutHandle = null; + } if (this.createPlotTimeoutHandle) { clearTimeout(this.createPlotTimeoutHandle); this.createPlotTimeoutHandle = null; @@ -829,7 +870,18 @@ export class TbFlot { useDashboardTimewindow: false, type: widgetType.latest, callbacks: { - onDataUpdated: (subscription) => this.thresholdsSourcesDataUpdated(subscription.data) + onDataUpdated: (subscription) => { + let allThresholds = deepClone(this.predefinedThresholds); + if (this.latestDataThresholds) { + allThresholds = allThresholds.concat(this.latestDataThresholds); + } + this.attributesThresholds = this.thresholdsSourcesDataUpdated(allThresholds, subscription.data); + this.options.grid.markings = allThresholds.concat(this.attributesThresholds); + if (this.plot) { + this.plot.getOptions().grid.markings = this.options.grid.markings; + this.updateData(); + } + } } }; this.ctx.subscriptionApi.createSubscription(thresholdsSourcesSubscriptionOptions, true).subscribe( @@ -839,28 +891,51 @@ export class TbFlot { ); } - private thresholdsSourcesDataUpdated(data: DatasourceData[]) { - const allThresholds = deepClone(this.predefinedThresholds); + private thresholdsSourcesDataUpdated(existingThresholds: TbFlotThresholdMarking[], data: DatasourceData[], + isLatest = false): TbFlotThresholdMarking[] { + const thresholds: TbFlotThresholdMarking[] = []; data.forEach((keyData) => { - if (keyData && keyData.data && keyData.data[0]) { + let skip = false; + let latestSettings: TbFlotLatestKeySettings; + if (isLatest) { + latestSettings = keyData.dataKey.settings; + if (!latestSettings.useAsThreshold) { + skip = true; + } + } + if (!skip && keyData && keyData.data && keyData.data[0]) { const attrValue = keyData.data[0][1]; if (isNumeric(attrValue) && isFinite(attrValue)) { - const settings: TbFlotThresholdKeySettings = keyData.dataKey.settings; - const colorIndex = this.subscription.data.length + allThresholds.length; - this.generateThreshold(allThresholds, settings.yaxis, settings.lineWidth, settings.color, colorIndex, attrValue); + let yaxis: number; + let lineWidth: number; + let color: string; + if (isLatest) { + yaxis = 1; + lineWidth = latestSettings.thresholdLineWidth; + color = latestSettings.thresholdColor || keyData.dataKey.color; + } else { + const settings: TbFlotThresholdKeySettings = keyData.dataKey.settings; + yaxis = settings.yaxis; + lineWidth = settings.lineWidth; + color = settings.color || keyData.dataKey.color; + } + const colorIndex = this.subscription.data.length + existingThresholds.length; + const threshold = this.generateThreshold(existingThresholds, yaxis, lineWidth, color, colorIndex, attrValue); + if (threshold != null) { + thresholds.push(threshold); + } } } }); - this.options.grid.markings = allThresholds; - this.redrawPlot(); + return thresholds; } private generateThreshold(existingThresholds: TbFlotThresholdMarking[], yaxis: number, lineWidth: number, - color: string, defaultColorIndex: number, thresholdValue: number) { + color: string, defaultColorIndex: number, thresholdValue: number): TbFlotThresholdMarking { const marking: TbFlotThresholdMarking = {}; let markingYAxis; - if (yaxis !== 1) { + if (isDefined(yaxis) && yaxis !== 1) { markingYAxis = 'y' + yaxis + 'axis'; } else { markingYAxis = 'yaxis'; @@ -885,8 +960,9 @@ export class TbFlot { return isEqual(existingMarking[markingYAxis], marking[markingYAxis]); }); if (!similarMarkings.length) { - existingThresholds.push(marking); + return marking; } + return null; } private subscribeForLabelPatternsSources(datasources: Datasource[]) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts index c81436c62b..ce6aaf5fd4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/circle.ts @@ -15,16 +15,15 @@ /// import L, { LeafletMouseEvent } from 'leaflet'; -import { CircleData, FormattedData, UnitedMapSettings } from '@home/components/widget/lib/maps/map-models'; +import { CircleData, UnitedMapSettings } from '@home/components/widget/lib/maps/map-models'; import { - fillPattern, functionValueCalculator, - parseWithTranslation, - processPattern, - safeExecute + parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils'; import LeafletMap from '@home/components/widget/lib/maps/leaflet-map'; import { createTooltip } from '@home/components/widget/lib/maps/maps-utils'; +import { FormattedData } from '@shared/models/widget.models'; +import { fillDataPattern, processDataPattern, safeExecute } from '@core/utils'; export class Circle { @@ -96,9 +95,9 @@ export class Circle { const pattern = this.settings.useCircleLabelFunction ? safeExecute(this.settings.circleLabelFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.circleLabel; this.map.circleLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); - this.map.replaceInfoTooltipCircle = processPattern(this.map.circleLabelText, this.data); + this.map.replaceInfoTooltipCircle = processDataPattern(this.map.circleLabelText, this.data); } - const circleLabelText = fillPattern(this.map.circleLabelText, this.map.replaceInfoTooltipCircle, this.data); + const circleLabelText = fillDataPattern(this.map.circleLabelText, this.map.replaceInfoTooltipCircle, this.data); this.leafletCircle.bindTooltip(`
${circleLabelText}
`, { className: 'tb-polygon-label', permanent: true, sticky: true, direction: 'center'}) .openTooltip(this.leafletCircle.getLatLng()); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts index 7c56288ae7..94f7e70ffb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { FormattedData, MapProviders, ReplaceInfo } from '@home/components/widget/lib/maps/map-models'; +import { MapProviders } from '@home/components/widget/lib/maps/map-models'; import { createLabelFromDatasource, hashCode, @@ -27,7 +27,7 @@ import { } from '@core/utils'; import { Observable, Observer, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { Datasource, DatasourceData } from '@shared/models/widget.models'; +import { FormattedData } from '@shared/models/widget.models'; import _ from 'lodash'; import { mapProviderSchema, providerSets } from '@home/components/widget/lib/maps/schemes'; import { addCondition, mergeSchemes } from '@core/schema-utils'; @@ -139,7 +139,7 @@ function createButtonElement(actionName: string, actionText: string) { return ``; } -function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any }, +function parseTemplate(template: string, data: FormattedData, translateFn?: TranslateFunc) { let res = ''; try { @@ -208,67 +208,6 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: return res; } -export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array { - const replaceInfo = []; - try { - const reg = /\${([^}]*)}/g; - let match = reg.exec(template); - while (match !== null) { - const variableInfo: ReplaceInfo = { - dataKeyName: '', - valDec: 2, - variable: '' - }; - const variable = match[0]; - let label = match[1]; - let valDec = 2; - const splitValues = label.split(':'); - if (splitValues.length > 1) { - label = splitValues[0]; - valDec = parseFloat(splitValues[1]); - } - - variableInfo.variable = variable; - variableInfo.valDec = valDec; - - if (label.startsWith('#')) { - const keyIndexStr = label.substring(1); - const n = Math.floor(Number(keyIndexStr)); - if (String(n) === keyIndexStr && n >= 0) { - variableInfo.dataKeyName = data.$datasource.dataKeys[n].label; - } - } else { - variableInfo.dataKeyName = label; - } - replaceInfo.push(variableInfo); - - match = reg.exec(template); - } - } catch (ex) { - console.log(ex, template); - } - return replaceInfo; -} - -export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array, data: FormattedData) { - let text = createLabelFromDatasource(data.$datasource, markerLabelText); - if (replaceInfoLabelMarker) { - for (const variableInfo of replaceInfoLabelMarker) { - let txtVal = ''; - if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) { - const varData = data[variableInfo.dataKeyName]; - if (isNumber(varData)) { - txtVal = padValue(varData, variableInfo.valDec); - } else { - txtVal = varData; - } - } - text = text.replace(variableInfo.variable, txtVal); - } - } - return text; -} - function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string { if (translateFn) { template = translateFn(template); @@ -308,7 +247,7 @@ export const parseWithTranslation = { throw Error('Translate not assigned'); } }, - parseTemplate(template: string, data: object, forceTranslate = false): string { + parseTemplate(template: string, data: FormattedData, forceTranslate = false): string { return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this)); }, prepareProcessPattern(template: string, forceTranslate = false): string { @@ -319,106 +258,6 @@ export const parseWithTranslation = { } }; -export function parseData(input: DatasourceData[], dataIndex?: number): FormattedData[] { - return _(input).groupBy(el => el?.datasource.entityName + el?.datasource.entityType) - .values().value().map((entityArray, i) => { - const obj: FormattedData = { - entityName: entityArray[0]?.datasource?.entityName, - entityId: entityArray[0].datasource.entityId, - entityType: entityArray[0].datasource.entityType, - $datasource: entityArray[0].datasource, - dsIndex: i, - deviceType: null - }; - entityArray.filter(el => el.data.length).forEach(el => { - const index = isDefined(dataIndex) ? dataIndex : el.data.length - 1; - if (!obj.hasOwnProperty(el.dataKey.label) || el.data[index][1] !== '') { - obj[el.dataKey.label] = el.data[index][1]; - obj[el.dataKey.label + '|ts'] = el.data[index][0]; - if (el.dataKey.label.toLowerCase() === 'type') { - obj.deviceType = el.data[index][1]; - } - } - }); - return obj; - }); -} - -export function flatData(input: FormattedData[]): FormattedData { - let result: FormattedData = {} as FormattedData; - if (input.length) { - for (const toMerge of input) { - result = {...result, ...toMerge}; - } - result.entityName = input[0].entityName; - result.entityId = input[0].entityId; - result.entityType = input[0].entityType; - result.$datasource = input[0].$datasource; - result.dsIndex = input[0].dsIndex; - result.deviceType = input[0].deviceType; - } - return result; -} - -export function parseArray(input: DatasourceData[]): FormattedData[][] { - return _(input).groupBy(el => el.datasource.entityName) - .values().value().map((entityArray, dsIndex) => { - const timeDataMap: {[time: number]: FormattedData} = {}; - entityArray.filter(e => e.data.length).forEach(entity => { - entity.data.forEach(tsData => { - const time = tsData[0]; - const value = tsData[1]; - let data = timeDataMap[time]; - if (!data) { - data = { - entityName: entity.datasource.entityName, - entityId: entity.datasource.entityId, - entityType: entity.datasource.entityType, - $datasource: entity.datasource, - dsIndex, - time, - deviceType: null - }; - timeDataMap[time] = data; - } - data[entity.dataKey.label] = value; - data[entity.dataKey.label + '|ts'] = time; - if (entity.dataKey.label.toLowerCase() === 'type') { - data.deviceType = value; - } - }); - }); - return _.values(timeDataMap); - }); -} - -export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any { - let res = null; - if (source?.length) { - try { - res = new Function(...params, source); - } - catch (err) { - res = null; - } - } - return res; -} - -export function safeExecute(func: (...args: any[]) => any, params = []) { - let res = null; - if (func && typeof (func) === 'function') { - try { - res = func(...params); - } - catch (err) { - console.log('error in external function:', err); - res = null; - } - } - return res; -} - export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: any) { let res; if (useFunction && isDefined(func) && isFunction(func)) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/dialogs/select-entity-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/dialogs/select-entity-dialog.component.ts index eab170d963..7573100a6c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/dialogs/select-entity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/dialogs/select-entity-dialog.component.ts @@ -21,7 +21,7 @@ import { AppState } from '@core/core.state'; import { Router } from '@angular/router'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { FormattedData } from '@home/components/widget/lib/maps/map-models'; +import { FormattedData } from '@shared/models/widget.models'; export interface SelectEntityDialogData { entities: FormattedData[]; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index db3bb6eab7..6207ddf762 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -23,14 +23,12 @@ import '@geoman-io/leaflet-geoman-free'; import { CircleData, defaultSettings, - FormattedData, MapSettings, MarkerIconInfo, MarkerImageInfo, MarkerSettings, PolygonSettings, PolylineSettings, - ReplaceInfo, UnitedMapSettings } from './map-models'; import { Marker } from './markers'; @@ -41,13 +39,17 @@ import { Circle } from './circle'; import { createTooltip, isCutPolygon, isJSON } from '@home/components/widget/lib/maps/maps-utils'; import { checkLngLat, - createLoadingDiv, - parseArray, - parseData, - safeExecute + createLoadingDiv } from '@home/components/widget/lib/maps/common-maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; -import { deepClone, isDefinedAndNotNull, isNotEmptyStr, isString } from '@core/utils'; +import { + deepClone, + formattedDataArrayFromDatasourceData, + formattedDataFormDatasourceData, + isDefinedAndNotNull, + isNotEmptyStr, + isString, mergeFormattedData, safeExecute +} from '@core/utils'; import { TranslateService } from '@ngx-translate/core'; import { SelectEntityDialogComponent, @@ -55,6 +57,7 @@ import { } from '@home/components/widget/lib/maps/dialogs/select-entity-dialog.component'; import { MatDialog } from '@angular/material/dialog'; import ITooltipsterInstance = JQueryTooltipster.ITooltipsterInstance; +import { FormattedData, ReplaceInfo } from '@shared/models/widget.models'; export default abstract class LeafletMap { @@ -641,13 +644,25 @@ export default abstract class LeafletMap { } updateData(drawRoutes: boolean, showPolygon: boolean) { + const data = this.ctx.data; + let formattedData = formattedDataFormDatasourceData(data); + if (this.ctx.latestData && this.ctx.latestData.length) { + const formattedLatestData = formattedDataFormDatasourceData(this.ctx.latestData); + formattedData = mergeFormattedData(formattedData, formattedLatestData); + } + let polyData: FormattedData[][] = null; + if (drawRoutes) { + polyData = formattedDataArrayFromDatasourceData(data); + } + this.updateFromData(drawRoutes, showPolygon, formattedData, polyData); + } + + updateFromData(drawRoutes: boolean, showPolygon: boolean, formattedData: FormattedData[], + polyData: FormattedData[][], markerClickCallback?: any) { this.drawRoutes = drawRoutes; this.showPolygon = showPolygon; if (this.map) { - const data = this.ctx.data; - const formattedData = parseData(data); if (drawRoutes) { - const polyData = parseArray(data); this.updatePolylines(polyData, formattedData, false); } if (showPolygon) { @@ -656,7 +671,7 @@ export default abstract class LeafletMap { if (this.options.showCircle) { this.updateCircle(formattedData, false); } - this.updateMarkers(formattedData, false); + this.updateMarkers(formattedData, false, markerClickCallback); this.updateBoundsInternal(); if (this.options.draggableMarker || this.editPolygons || this.editCircle) { let foundEntityWithLocation = false; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 13fae275ec..9caf7f2c13 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -14,8 +14,7 @@ /// limitations under the License. /// -import { Datasource } from '@app/shared/models/widget.models'; -import { EntityType } from '@shared/models/entity-type.models'; +import { Datasource, FormattedData } from '@app/shared/models/widget.models'; import tinycolor from 'tinycolor2'; import { BaseIconOptions, Icon } from 'leaflet'; @@ -122,22 +121,6 @@ export type MarkerSettings = { tooltipOffsetY: number; }; -export interface FormattedData { - $datasource: Datasource; - entityName: string; - entityId: string; - entityType: EntityType; - dsIndex: number; - deviceType: string; - [key: string]: any; -} - -export interface ReplaceInfo { - variable: string; - valDec?: number; - dataKeyName: string; -} - export type PolygonSettings = { showPolygon: boolean; polygonKeyName: string; @@ -238,7 +221,7 @@ export interface MapImage { update?: boolean; } -export interface TripAnimationSettings extends PolygonSettings { +export interface TripAnimationSettings extends PolygonSettings, CircleSettings { showPoints: boolean; pointColor: string; pointSize: number; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index 524184484d..f2233ce236 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { defaultSettings, FormattedData, hereProviders, MapProviders, UnitedMapSettings } from './map-models'; +import { defaultSettings, hereProviders, MapProviders, UnitedMapSettings } from './map-models'; import LeafletMap from './leaflet-map'; import { commonMapSettingsSchema, @@ -28,13 +28,19 @@ import { import { MapWidgetInterface, MapWidgetStaticInterface } from './map-widget.interface'; import { addCondition, addGroupInfo, addToSchema, initSchema, mergeSchemes } from '@core/schema-utils'; import { WidgetContext } from '@app/modules/home/models/widget-component.models'; -import { getDefCenterPosition, getProviderSchema, parseFunction, parseWithTranslation } from './common-maps-utils'; -import { Datasource, DatasourceData, JsonSettingsSchema, WidgetActionDescriptor } from '@shared/models/widget.models'; +import { getDefCenterPosition, getProviderSchema, parseWithTranslation } from './common-maps-utils'; +import { + Datasource, + DatasourceData, + FormattedData, + JsonSettingsSchema, + WidgetActionDescriptor +} from '@shared/models/widget.models'; import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; import { EntityDataPageLink } from '@shared/models/query/query.models'; import { providerClass } from '@home/components/widget/lib/maps/providers'; -import { isDefined } from '@core/utils'; +import { isDefined, parseFunction } from '@core/utils'; import L from 'leaflet'; import { forkJoin, Observable, of } from 'rxjs'; import { AttributeService } from '@core/http/attribute.service'; @@ -220,8 +226,12 @@ export class MapWidgetController implements MapWidgetInterface { id: e.$datasource.entityId }; + let dataKeys = e.$datasource.dataKeys; + if (e.$datasource.latestDataKeys) { + dataKeys = dataKeys.concat(e.$datasource.latestDataKeys); + } for (const dataKeyName of Object.keys(values)) { - for (const key of e.$datasource.dataKeys) { + for (const key of dataKeys) { if (dataKeyName === key.name) { const value = { key: key.name, @@ -311,6 +321,10 @@ export class MapWidgetController implements MapWidgetInterface { this.map.setLoading(false); } + latestDataUpdate() { + this.map.updateData(this.drawRoutes, this.settings.showPolygon); + } + resize() { this.map.onResize(); this.map?.invalidateSize(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index 24bfd8dbb4..8d2262813a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -16,7 +16,6 @@ import L, { LeafletMouseEvent } from 'leaflet'; import { - FormattedData, MarkerIconInfo, MarkerIconReadyFunction, MarkerImageInfo, @@ -24,10 +23,11 @@ import { UnitedMapSettings } from './map-models'; import { bindPopupActions, createTooltip } from './maps-utils'; -import { aspectCache, fillPattern, parseWithTranslation, processPattern, safeExecute } from './common-maps-utils'; +import { aspectCache, parseWithTranslation } from './common-maps-utils'; import tinycolor from 'tinycolor2'; -import { isDefined, isDefinedAndNotNull } from '@core/utils'; +import { fillDataPattern, isDefined, isDefinedAndNotNull, processDataPattern, safeExecute } from '@core/utils'; import LeafletMap from './leaflet-map'; +import { FormattedData } from '@shared/models/widget.models'; export class Marker { @@ -102,9 +102,9 @@ export class Marker { const pattern = this.settings.useTooltipFunction ? safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; this.map.markerTooltipText = parseWithTranslation.prepareProcessPattern(pattern, true); - this.map.replaceInfoTooltipMarker = processPattern(this.map.markerTooltipText, data); + this.map.replaceInfoTooltipMarker = processDataPattern(this.map.markerTooltipText, data); } - this.tooltip.setContent(fillPattern(this.map.markerTooltipText, this.map.replaceInfoTooltipMarker, data)); + this.tooltip.setContent(fillDataPattern(this.map.markerTooltipText, this.map.replaceInfoTooltipMarker, data)); if (this.tooltip.isOpen() && this.tooltip.getElement()) { bindPopupActions(this.tooltip, this.settings, data.$datasource); } @@ -124,9 +124,9 @@ export class Marker { const pattern = settings.useLabelFunction ? safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label; this.map.markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); - this.map.replaceInfoLabelMarker = processPattern(this.map.markerLabelText, this.data); + this.map.replaceInfoLabelMarker = processDataPattern(this.map.markerLabelText, this.data); } - settings.labelText = fillPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data); + settings.labelText = fillDataPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data); this.leafletMarker.bindTooltip(`
${settings.labelText}
`, { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.labelOffset }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts index 527ef1056f..108e4871c4 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts @@ -17,13 +17,12 @@ import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet'; import { createTooltip, isCutPolygon } from './maps-utils'; import { - fillPattern, functionValueCalculator, - parseWithTranslation, - processPattern, - safeExecute + parseWithTranslation } from './common-maps-utils'; -import { FormattedData, PolygonSettings, UnitedMapSettings } from './map-models'; +import { PolygonSettings, UnitedMapSettings } from './map-models'; +import { FormattedData } from '@shared/models/widget.models'; +import { fillDataPattern, processDataPattern, safeExecute } from '@core/utils'; export class Polygon { @@ -104,9 +103,9 @@ export class Polygon { const pattern = settings.usePolygonLabelFunction ? safeExecute(settings.polygonLabelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.polygonLabel; this.map.polygonLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); - this.map.replaceInfoLabelPolygon = processPattern(this.map.polygonLabelText, this.data); + this.map.replaceInfoLabelPolygon = processDataPattern(this.map.polygonLabelText, this.data); } - const polygonLabelText = fillPattern(this.map.polygonLabelText, this.map.replaceInfoLabelPolygon, this.data); + const polygonLabelText = fillDataPattern(this.map.polygonLabelText, this.map.replaceInfoLabelPolygon, this.data); this.leafletPoly.bindTooltip(`
${polygonLabelText}
`, { className: 'tb-polygon-label', permanent: true, sticky: true, direction: 'center' }) .openTooltip(this.leafletPoly.getBounds().getCenter()); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts index 0c3b48d68e..8d3496a037 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts @@ -18,8 +18,9 @@ import L, { PolylineDecorator, PolylineDecoratorOptions, Symbol } from 'leaflet'; import 'leaflet-polylinedecorator'; -import { FormattedData, PolylineSettings } from './map-models'; +import { PolylineSettings } from './map-models'; import { functionValueCalculator } from '@home/components/widget/lib/maps/common-maps-utils'; +import { FormattedData } from '@shared/models/widget.models'; export class Polyline { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index b527691ed9..d0678dea8b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -18,17 +18,16 @@ import L, { LatLngBounds, LatLngLiteral, LatLngTuple } from 'leaflet'; import LeafletMap from '../leaflet-map'; import { CircleData, MapImage, PosFuncton, UnitedMapSettings } from '../map-models'; import { Observable, ReplaySubject } from 'rxjs'; -import { filter, map, mergeMap } from 'rxjs/operators'; +import { map, mergeMap } from 'rxjs/operators'; import { aspectCache, - calculateNewPointCoordinate, - parseFunction + calculateNewPointCoordinate } from '@home/components/widget/lib/maps/common-maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { isDefinedAndNotNull, isEmptyStr } from '@core/utils'; +import { isDefinedAndNotNull, isEmptyStr, parseFunction } from '@core/utils'; import { EntityDataPageLink } from '@shared/models/query/query.models'; const maxZoom = 4; // ? diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts index a4d93b98f8..54988775c3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/markdown-widget.component.ts @@ -19,17 +19,18 @@ import { PageComponent } from '@shared/components/page.component'; import { WidgetContext } from '@home/models/widget-component.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { DatasourceData } from '@shared/models/widget.models'; +import { DatasourceData, FormattedData } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { - fillPattern, flatData, - parseData, - parseFunction, - processPattern, + createLabelFromPattern, + fillDataPattern, + flatFormattedData, + formattedDataFormDatasourceData, + hashCode, + isNotEmptyStr, + parseFunction, processDataPattern, safeExecute -} from '@home/components/widget/lib/maps/common-maps-utils'; -import { FormattedData } from '@home/components/widget/lib/maps/map-models'; -import { hashCode, isNotEmptyStr } from '@core/utils'; +} from '@core/utils'; import cssjs from '@core/css/css'; import { UtilsService } from '@core/services/utils.service'; import { HOME_COMPONENTS_MODULE_TOKEN } from '@home/components/tokens'; @@ -113,12 +114,11 @@ export class MarkdownWidgetComponent extends PageComponent implements OnInit { } else { initialData = []; } - const data = parseData(initialData); + const data = formattedDataFormDatasourceData(initialData); let markdownText = this.settings.useMarkdownTextFunction ? safeExecute(this.markdownTextFunction, [data]) : this.settings.markdownTextPattern; - const allData = flatData(data); - const replaceInfo = processPattern(markdownText, allData); - markdownText = fillPattern(markdownText, replaceInfo, allData); + const allData = flatFormattedData(data); + markdownText = createLabelFromPattern(markdownText, allData); if (this.markdownText !== markdownText) { this.markdownText = this.utils.customTranslation(markdownText, markdownText); this.cd.detectChanges(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts index b16d307e32..07e5334419 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/qrcode-widget.component.ts @@ -19,17 +19,17 @@ import { PageComponent } from '@shared/components/page.component'; import { WidgetContext } from '@home/models/widget-component.models'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; +import { DatasourceData, FormattedData } from '@shared/models/widget.models'; +import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { - fillPattern, flatData, - parseData, + createLabelFromPattern, + flatFormattedData, + formattedDataFormDatasourceData, + isNumber, + isObject, parseFunction, - processPattern, safeExecute -} from '@home/components/widget/lib/maps/common-maps-utils'; -import { FormattedData } from '@home/components/widget/lib/maps/map-models'; -import { DatasourceData } from '@shared/models/widget.models'; -import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; -import { isNumber, isObject } from '@core/utils'; +} from '@core/utils'; interface QrCodeWidgetSettings { qrCodeTextPattern: string; @@ -98,12 +98,11 @@ export class QrCodeWidgetComponent extends PageComponent implements OnInit, Afte } else { initialData = []; } - const data = parseData(initialData); + const data = formattedDataFormDatasourceData(initialData); const pattern = this.settings.useQrCodeTextFunction ? safeExecute(this.qrCodeTextFunction, [data]) : this.settings.qrCodeTextPattern; - const allData = flatData(data); - const replaceInfo = processPattern(pattern, allData); - qrCodeText = fillPattern(pattern, replaceInfo, allData); + const allData = flatFormattedData(data); + qrCodeText = createLabelFromPattern(pattern, allData); this.updateQrCodeText(qrCodeText); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index e4df1cd311..0747ddffc1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -15,14 +15,13 @@ /// import { EntityId } from '@shared/models/id/entity-id'; -import { DataKey, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; +import { DataKey, FormattedData, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; import { getDescendantProp, isDefined, isNotEmptyStr } from '@core/utils'; import { AlarmDataInfo, alarmFields } from '@shared/models/alarm.models'; import * as tinycolor_ from 'tinycolor2'; import { Direction, EntityDataSortOrder, EntityKey } from '@shared/models/query/query.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { WidgetContext } from '@home/models/widget-component.models'; -import { FormattedData } from '@home/components/widget/lib/maps/map-models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index 19f2429ff4..02ab76d829 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -47,15 +47,15 @@ Timestamp + [innerHTML]="cellContent(source, null, 0, row, row[0], rowIndex)" + [ngStyle]="cellStyle(source, null, 0, row, row[0], rowIndex)"> {{ h.dataKey.label }} + [innerHTML]="cellContent(source, h, h.index, row, row[h.index], rowIndex)" + [ngStyle]="cellStyle(source, h, h.index, row, row[h.index], rowIndex)"> diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index 427060d39a..49181f9338 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -40,7 +40,15 @@ import { } from '@shared/models/widget.models'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; -import { hashCode, isDefined, isNumber, isObject, isUndefined } from '@core/utils'; +import { + formattedDataFormDatasourceData, + hashCode, + isDefined, + isDefinedAndNotNull, + isNumber, + isObject, + isUndefined +} from '@core/utils'; import cssjs from '@core/css/css'; import { PageLink } from '@shared/models/page/page-link'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; @@ -70,7 +78,6 @@ import { import { Overlay } from '@angular/cdk/overlay'; import { SubscriptionEntityInfo } from '@core/api/widget-api.models'; import { DatePipe } from '@angular/common'; -import { parseData } from '@home/components/widget/lib/maps/common-maps-utils'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ResizeObserver } from '@juggle/resize-observer'; import { hidePageSizePixelValue } from '@shared/models/constants'; @@ -81,6 +88,11 @@ export interface TimeseriesTableWidgetSettings extends TableWidgetSettings { hideEmptyLines: boolean; } +interface TimeseriesWidgetLatestDataKeySettings extends TableWidgetDataKeySettings { + show: boolean; + order: number; +} + interface TimeseriesRow { actionCellButtons?: TableCellButtonActionDescriptor[]; hasActions?: boolean; @@ -92,20 +104,25 @@ interface TimeseriesHeader { index: number; dataKey: DataKey; sortable: boolean; + show: boolean; + styleInfo: CellStyleInfo; + contentInfo: CellContentInfo; + order?: number; } interface TimeseriesTableSource { keyStartIndex: number; keyEndIndex: number; + latestKeyStartIndex: number; + latestKeyEndIndex: number; datasource: Datasource; rawData: Array; + latestRawData: Array; data: TimeseriesRow[]; pageLink: PageLink; displayedColumns: string[]; timeseriesDatasource: TimeseriesDatasource; header: TimeseriesHeader[]; - stylesInfo: CellStyleInfo[]; - contentsInfo: CellContentInfo[]; rowDataTemplate: {[key: string]: any}; } @@ -142,6 +159,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI private settings: TimeseriesTableWidgetSettings; private widgetConfig: WidgetConfig; private data: Array; + private latestData: Array; private datasources: Array; private defaultPageSize = 10; @@ -183,6 +201,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.settings = this.ctx.settings; this.widgetConfig = this.ctx.widgetConfig; this.data = this.ctx.data; + this.latestData = this.ctx.latestData; this.datasources = this.ctx.datasources; this.initialize(); this.ctx.updateWidgetParams(); @@ -249,6 +268,12 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.ctx.detectChanges(); } + public onLatestDataUpdated() { + this.updateCurrentSourceLatestData(); + this.clearCache(); + this.ctx.detectChanges(); + } + private initialize() { this.ctx.widgetActions = [this.searchAction ]; @@ -302,56 +327,94 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.sources = []; this.sourceIndex = 0; let keyOffset = 0; + let latestKeyOffset = 0; const pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; if (this.datasources) { for (const datasource of this.datasources) { const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder); const source = {} as TimeseriesTableSource; + source.header = this.prepareHeader(datasource); source.keyStartIndex = keyOffset; keyOffset += datasource.dataKeys.length; source.keyEndIndex = keyOffset; + source.latestKeyStartIndex = latestKeyOffset; + latestKeyOffset += datasource.latestDataKeys ? datasource.latestDataKeys.length : 0; + source.latestKeyEndIndex = latestKeyOffset; source.datasource = datasource; source.data = []; source.rawData = []; source.displayedColumns = []; source.pageLink = new PageLink(pageSize, 0, null, sortOrder); - source.header = []; - source.stylesInfo = []; - source.contentsInfo = []; source.rowDataTemplate = {}; source.rowDataTemplate.Timestamp = null; if (this.showTimestamp) { source.displayedColumns.push('0'); } - for (let a = 0; a < datasource.dataKeys.length; a++ ) { - const dataKey = datasource.dataKeys[a]; - const keySettings: TableWidgetDataKeySettings = dataKey.settings; - const sortable = !dataKey.usePostProcessing; - const index = a + 1; - source.header.push({ - index, - dataKey, - sortable - }); - source.displayedColumns.push(index + ''); + source.header.forEach(header => { + const dataKey = header.dataKey; + if (header.show) { + source.displayedColumns.push(header.index + ''); + } source.rowDataTemplate[dataKey.label] = null; - source.stylesInfo.push(getCellStyleInfo(keySettings, 'value, rowData, ctx')); - const cellContentInfo = getCellContentInfo(keySettings, 'value, rowData, ctx'); - cellContentInfo.units = dataKey.units; - cellContentInfo.decimals = dataKey.decimals; - source.contentsInfo.push(cellContentInfo); - } + }); if (this.setCellButtonAction) { source.displayedColumns.push('actions'); } const tsDatasource = new TimeseriesDatasource(source, this.hideEmptyLines, this.dateFormatFilter, this.datePipe, this.ctx); - tsDatasource.dataUpdated(this.data); + tsDatasource.allDataUpdated(this.data, this.latestData); this.sources.push(source); } } this.updateActiveEntityInfo(); } + private prepareHeader(datasource: Datasource): TimeseriesHeader[] { + const dataKeys = datasource.dataKeys; + const latestDataKeys = datasource.latestDataKeys; + let header: TimeseriesHeader[] = []; + dataKeys.forEach((dataKey, index) => { + const sortable = !dataKey.usePostProcessing; + const keySettings: TableWidgetDataKeySettings = dataKey.settings; + const styleInfo = getCellStyleInfo(keySettings, 'value, rowData, ctx'); + const contentInfo = getCellContentInfo(keySettings, 'value, rowData, ctx'); + contentInfo.units = dataKey.units; + contentInfo.decimals = dataKey.decimals; + header.push({ + index: index + 1, + dataKey, + sortable, + styleInfo, + contentInfo, + show: true, + order: index + 2 + }); + }); + if (latestDataKeys) { + latestDataKeys.forEach((dataKey, latestIndex) => { + const index = dataKeys.length + latestIndex; + const sortable = !dataKey.usePostProcessing; + const keySettings: TimeseriesWidgetLatestDataKeySettings = dataKey.settings; + const styleInfo = getCellStyleInfo(keySettings, 'value, rowData, ctx'); + const contentInfo = getCellContentInfo(keySettings, 'value, rowData, ctx'); + contentInfo.units = dataKey.units; + contentInfo.decimals = dataKey.decimals; + header.push({ + index: index + 1, + dataKey, + sortable, + styleInfo, + contentInfo, + show: isDefinedAndNotNull(keySettings.show) ? keySettings.show : true, + order: isDefinedAndNotNull(keySettings.order) ? keySettings.order : (index + 2) + }); + }); + } + header = header.sort((a, b) => { + return a.order - b.order; + }); + return header; + } + private updateActiveEntityInfo() { const source = this.sources[this.sourceIndex]; let activeEntityInfo: SubscriptionEntityInfo = null; @@ -393,7 +456,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI } onSourceIndexChanged() { - this.updateCurrentSourceData(); + this.updateCurrentSourceAllData(); this.updateActiveEntityInfo(); this.clearCache(); } @@ -466,7 +529,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI const rowData = source.rowDataTemplate; rowData.Timestamp = row[0]; source.header.forEach((headerInfo) => { - rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + rowData[headerInfo.dataKey.label] = row[headerInfo.index]; }); res = this.rowStylesInfo.rowStyleFunction(rowData, this.ctx); if (!isObject(res)) { @@ -486,19 +549,20 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI return res; } - public cellStyle(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any, rowIndex: number): any { + public cellStyle(source: TimeseriesTableSource, header: TimeseriesHeader, + index: number, row: TimeseriesRow, value: any, rowIndex: number): any { const cacheIndex = rowIndex * (source.header.length + 1) + index; let res = this.cellStyleCache[cacheIndex]; if (!res) { res = {}; if (index > 0) { - const styleInfo = source.stylesInfo[index - 1]; + const styleInfo = header.styleInfo; if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { try { const rowData = source.rowDataTemplate; rowData.Timestamp = row[0]; source.header.forEach((headerInfo) => { - rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + rowData[headerInfo.dataKey.label] = row[headerInfo.index]; }); res = styleInfo.cellStyleFunction(value, rowData, this.ctx); if (!isObject(res)) { @@ -519,7 +583,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI return res; } - public cellContent(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any, rowIndex: number): SafeHtml { + public cellContent(source: TimeseriesTableSource, header: TimeseriesHeader, + index: number, row: TimeseriesRow, value: any, rowIndex: number): SafeHtml { const cacheIndex = rowIndex * (source.header.length + 1) + index ; let res = this.cellContentCache[cacheIndex]; if (isUndefined(res)) { @@ -528,13 +593,13 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI res = row.formattedTs; } else { let content; - const contentInfo = source.contentsInfo[index - 1]; + const contentInfo = header.contentInfo; if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { try { const rowData = source.rowDataTemplate; rowData.Timestamp = row[0]; source.header.forEach((headerInfo) => { - rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + rowData[headerInfo.dataKey.label] = row[headerInfo.index]; }); content = contentInfo.cellContentFunction(value, rowData, this.ctx); } catch (e) { @@ -599,10 +664,18 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI return index === this.sourceIndex; } + private updateCurrentSourceAllData() { + this.sources[this.sourceIndex].timeseriesDatasource.allDataUpdated(this.data, this.latestData); + } + private updateCurrentSourceData() { this.sources[this.sourceIndex].timeseriesDatasource.dataUpdated(this.data); } + private updateCurrentSourceLatestData() { + this.sources[this.sourceIndex].timeseriesDatasource.latestDataUpdated(this.latestData); + } + private loadCurrentSourceRow() { this.sources[this.sourceIndex].timeseriesDatasource.loadRows(); this.clearCache(); @@ -668,17 +741,28 @@ class TimeseriesDatasource implements DataSource { ); } + allDataUpdated(data: DatasourceData[], latestData: DatasourceData[]) { + this.source.rawData = data.slice(this.source.keyStartIndex, this.source.keyEndIndex); + this.source.latestRawData = latestData.slice(this.source.latestKeyStartIndex, this.source.latestKeyEndIndex); + this.updateSourceData(); + } + dataUpdated(data: DatasourceData[]) { this.source.rawData = data.slice(this.source.keyStartIndex, this.source.keyEndIndex); this.updateSourceData(); } + latestDataUpdated(latestData: DatasourceData[]) { + this.source.latestRawData = latestData.slice(this.source.latestKeyStartIndex, this.source.latestKeyEndIndex); + this.updateSourceData(); + } + private updateSourceData() { - this.source.data = this.convertData(this.source.rawData); + this.source.data = this.convertData(this.source.rawData, this.source.latestRawData); this.allRowsSubject.next(this.source.data); } - private convertData(data: DatasourceData[]): TimeseriesRow[] { + private convertData(data: DatasourceData[], latestData: DatasourceData[]): TimeseriesRow[] { const rowsMap: {[timestamp: number]: TimeseriesRow} = {}; for (let d = 0; d < data.length; d++) { const columnData = data[d].data; @@ -691,9 +775,9 @@ class TimeseriesDatasource implements DataSource { }; if (this.cellButtonActions.length) { if (this.usedShowCellActionFunction) { - const parsedData = parseData(data, index); + const parsedData = formattedDataFormDatasourceData(data, index); row.actionCellButtons = prepareTableCellButtonActions(this.widgetContext, this.cellButtonActions, - parsedData[0], this.reserveSpaceForHiddenAction); + parsedData[0], this.reserveSpaceForHiddenAction); row.hasActions = checkHasActions(row.actionCellButtons); } else { row.hasActions = true; @@ -701,7 +785,7 @@ class TimeseriesDatasource implements DataSource { } } row[0] = timestamp; - for (let c = 0; c < data.length; c++) { + for (let c = 0; c < (data.length + latestData.length); c++) { row[c + 1] = undefined; } rowsMap[timestamp] = row; @@ -726,6 +810,15 @@ class TimeseriesDatasource implements DataSource { } else { rows = Object.keys(rowsMap).map(itm => rowsMap[itm]); } + for (let d = 0; d < latestData.length; d++) { + const columnData = latestData[d].data; + if (columnData.length) { + const value = columnData[0][1]; + rows.forEach((row) => { + row[data.length + d + 1] = value; + }); + } + } return rows; } diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts index 910d76787f..7db38e2416 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts @@ -27,9 +27,10 @@ import { SecurityContext, ViewChild } from '@angular/core'; -import { FormattedData, MapProviders, TripAnimationSettings } from '@home/components/widget/lib/maps/map-models'; +import { MapProviders, TripAnimationSettings } from '@home/components/widget/lib/maps/map-models'; import { addCondition, addGroupInfo, addToSchema, initSchema } from '@app/core/schema-utils'; import { + mapCircleSchema, mapPolygonSchema, pathSchema, pointSchema, @@ -42,14 +43,18 @@ import { getProviderSchema, getRatio, interpolateOnLineSegment, - parseArray, - parseFunction, - parseWithTranslation, - safeExecute + parseWithTranslation } from '@home/components/widget/lib/maps/common-maps-utils'; -import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models'; +import { FormattedData, JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models'; import moment from 'moment'; -import { deepClone, isDefined, isUndefined } from '@core/utils'; +import { + deepClone, + formattedDataArrayFromDatasourceData, formattedDataFormDatasourceData, + isDefined, + isUndefined, mergeFormattedData, + parseFunction, + safeExecute +} from '@core/utils'; import { ResizeObserver } from '@juggle/resize-observer'; import { MapWidgetInterface } from '@home/components/widget/lib/maps/map-widget.interface'; @@ -78,6 +83,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy normalizationStep: number; interpolatedTimeData: {[time: number]: FormattedData}[] = []; formattedInterpolatedTimeData: FormattedData[][] = []; + formattedCurrentPosition: FormattedData[] = []; + formattedLatestData: FormattedData[] = []; widgetConfig: WidgetConfig; settings: TripAnimationSettings; mainTooltips = []; @@ -100,11 +107,10 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy addGroupInfo(schema, 'Path Settings'); addToSchema(schema, addCondition(pointSchema, 'model.showPoints === true', ['showPoints'])); addGroupInfo(schema, 'Path Points Settings'); - const mapPolygonSchemaWithoutEdit = deepClone(mapPolygonSchema); - delete mapPolygonSchemaWithoutEdit.schema.properties.editablePolygon; - mapPolygonSchemaWithoutEdit.form.splice(mapPolygonSchemaWithoutEdit.form.indexOf('editablePolygon'), 1); - addToSchema(schema, addCondition(mapPolygonSchemaWithoutEdit, 'model.showPolygon === true', ['showPolygon'])); + addToSchema(schema, addCondition(mapPolygonSchema, 'model.showPolygon === true', ['showPolygon'])); addGroupInfo(schema, 'Polygon Settings'); + addToSchema(schema, addCondition(mapCircleSchema, 'model.showCircle === true', ['showCircle'])); + addGroupInfo(schema, 'Circle Settings'); return schema; } @@ -118,7 +124,6 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy rotationAngle: 0 }; this.settings = { ...settings, ...this.ctx.settings }; - this.settings.editablePolygon = false; this.useAnchors = this.settings.showPoints && this.settings.usePointAsAnchor; this.settings.pointAsAnchorFunction = parseFunction(this.settings.pointAsAnchorFunction, ['data', 'dsData', 'dsIndex']); this.settings.tooltipFunction = parseFunction(this.settings.tooltipFunction, ['data', 'dsData', 'dsIndex']); @@ -127,7 +132,8 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy this.normalizationStep = this.settings.normalizationStep; const subscription = this.ctx.defaultSubscription; subscription.callbacks.onDataUpdated = () => { - this.historicalData = parseArray(this.ctx.data).map(item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length); + this.historicalData = formattedDataArrayFromDatasourceData(this.ctx.data).map( + item => this.clearIncorrectFirsLastDatapoint(item)).filter(arr => arr.length); this.interpolatedTimeData.length = 0; this.formattedInterpolatedTimeData.length = 0; if (this.historicalData.length) { @@ -143,6 +149,10 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy this.mapWidget.map.setLoading(false); this.cd.detectChanges(); }; + subscription.callbacks.onLatestDataUpdated = () => { + this.formattedLatestData = formattedDataFormDatasourceData(this.ctx.latestData); + this.updateCurrentData(); + }; } ngAfterViewInit() { @@ -166,17 +176,17 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy timeUpdated(time: number) { this.currentTime = time; // get point for each datasource associated with time - const currentPosition = this.interpolatedTimeData + this.formattedCurrentPosition = this.interpolatedTimeData .map(dataSource => dataSource[time]); for (let j = 0; j < this.interpolatedTimeData.length; j++) { - if (isUndefined(currentPosition[j])) { + if (isUndefined(this.formattedCurrentPosition[j])) { const timePoints = Object.keys(this.interpolatedTimeData[j]).map(item => parseInt(item, 10)); for (let i = 1; i < timePoints.length; i++) { if (timePoints[i - 1] < time && timePoints[i] > time) { const beforePosition = this.interpolatedTimeData[j][timePoints[i - 1]]; const afterPosition = this.interpolatedTimeData[j][timePoints[i]]; const ratio = getRatio(timePoints[i - 1], timePoints[i], time); - currentPosition[j] = { + this.formattedCurrentPosition[j] = { ...beforePosition, time, ...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio) @@ -187,25 +197,29 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy } } for (let j = 0; j < this.interpolatedTimeData.length; j++) { - if (isUndefined(currentPosition[j])) { - currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time); + if (isUndefined(this.formattedCurrentPosition[j])) { + this.formattedCurrentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time); } } + this.updateCurrentData(); + } + + private updateCurrentData() { + let currentPosition = this.formattedCurrentPosition; + if (this.formattedLatestData.length) { + currentPosition = mergeFormattedData(this.formattedCurrentPosition, this.formattedLatestData); + } this.calcLabel(currentPosition); this.calcMainTooltip(currentPosition); if (this.mapWidget && this.mapWidget.map && this.mapWidget.map.map) { - this.mapWidget.map.updatePolylines(this.formattedInterpolatedTimeData, currentPosition, true); - if (this.settings.showPolygon) { - this.mapWidget.map.updatePolygons(currentPosition); - } - if (this.settings.showPoints) { - this.mapWidget.map.updatePoints(this.formattedInterpolatedTimeData, this.calcTooltip); - } - this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => { + this.mapWidget.map.updateFromData(true, this.settings.showPolygon, currentPosition, this.formattedInterpolatedTimeData, (trip) => { this.activeTrip = trip; this.timeUpdated(this.currentTime); this.cd.markForCheck(); }); + if (this.settings.showPoints) { + this.mapWidget.map.updatePoints(this.formattedInterpolatedTimeData, this.calcTooltip); + } } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index de88b73e3b..ca23a2efd1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -107,6 +107,7 @@ export class WidgetComponentService { controllerScript: this.utils.editWidgetInfo.controllerScript, settingsSchema: this.utils.editWidgetInfo.settingsSchema, dataKeySettingsSchema: this.utils.editWidgetInfo.dataKeySettingsSchema, + latestDataKeySettingsSchema: this.utils.editWidgetInfo.latestDataKeySettingsSchema, defaultConfig: this.utils.editWidgetInfo.defaultConfig }, new WidgetTypeId('1'), new TenantId( NULL_UUID ), 'customWidgetBundle', undefined ); @@ -286,6 +287,9 @@ export class WidgetComponentService { if (widgetControllerDescriptor.dataKeySettingsSchema) { widgetInfo.typeDataKeySettingsSchema = widgetControllerDescriptor.dataKeySettingsSchema; } + if (widgetControllerDescriptor.latestDataKeySettingsSchema) { + widgetInfo.typeLatestDataKeySettingsSchema = widgetControllerDescriptor.latestDataKeySettingsSchema; + } widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters; widgetInfo.actionSources = widgetControllerDescriptor.actionSources; widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction; @@ -465,6 +469,9 @@ export class WidgetComponentService { if (isFunction(widgetTypeInstance.getDataKeySettingsSchema)) { result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema(); } + if (isFunction(widgetTypeInstance.getLatestDataKeySettingsSchema)) { + result.latestDataKeySettingsSchema = widgetTypeInstance.getLatestDataKeySettingsSchema(); + } if (isFunction(widgetTypeInstance.typeParameters)) { result.typeParameters = widgetTypeInstance.typeParameters(); } else { @@ -487,6 +494,9 @@ export class WidgetComponentService { if (isUndefined(result.typeParameters.singleEntity)) { result.typeParameters.singleEntity = false; } + if (isUndefined(result.typeParameters.hasAdditionalLatestDataKeys)) { + result.typeParameters.hasAdditionalLatestDataKeys = false; + } if (isUndefined(result.typeParameters.warnOnPageDataOverflow)) { result.typeParameters.warnOnPageDataOverflow = true; } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index bb4ff8503e..65b80d7367 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -91,6 +91,7 @@
widget-config.datasources
{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}
+
@@ -132,82 +133,96 @@ {{$index + 1}}.
-
-
- - - - {{ datasourceTypesTranslations.get(datasourceType) | translate }} - - - -
- - - - - - - +
+
+ + + + {{ datasourceTypesTranslations.get(datasourceType) | translate }} + + + +
+ + + datasource.label + + + + - - - - - - - - - + + + + + + + + + +
+
+ + + + +
- - -
- + +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss index f18743aa9c..014e5b65a8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.scss @@ -61,6 +61,16 @@ max-width: 200px; } } + .tb-datasource-params { + position: relative; + padding: 0 0 0 10px; + margin: 5px; + tb-error.tb-datasource-error { + position: absolute; + bottom: 4px; + left: 8px; + } + } .tb-data-keys { @media #{$mat-gt-sm} { padding-left: 8px; diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index c34698bafd..9a1e927b55 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -25,7 +25,7 @@ import { datasourceTypeTranslationMap, defaultLegendConfig, GroupInfo, - JsonSchema, + JsonSchema, JsonSettingsSchema, widgetType } from '@shared/models/widget.models'; import { @@ -171,6 +171,10 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont public advancedSettings: FormGroup; public actionsSettings: FormGroup; + public dataError = ''; + + public datasourceError: string[] = []; + private dataSettingsChangesSubscription: Subscription; private targetDeviceSettingsSubscription: Subscription; private alarmSourceSettingsSubscription: Subscription; @@ -567,9 +571,17 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont } } + public dataKeysOptional(datasource?: Datasource): boolean { + if (this.widgetType === widgetType.timeseries && this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) { + return true; + } else { + return this.modelValue.typeParameters && this.modelValue.typeParameters.dataKeysOptional + && datasource?.type !== DatasourceType.entityCount; + } + } + private buildDatasourceForm(datasource?: Datasource): FormGroup { - let dataKeysRequired = !this.modelValue.typeParameters || !this.modelValue.typeParameters.dataKeysOptional - || datasource?.type === DatasourceType.entityCount; + const dataKeysRequired = !this.dataKeysOptional(datasource); const datasourceFormGroup = this.fb.group( { type: [datasource ? datasource.type : null, [Validators.required]], @@ -581,13 +593,15 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont dataKeys: [datasource ? datasource.dataKeys : null, dataKeysRequired ? [Validators.required] : []] } ); + if (this.widgetType === widgetType.timeseries && this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) { + datasourceFormGroup.addControl('latestDataKeys', this.fb.control(datasource ? datasource.latestDataKeys : null)); + } datasourceFormGroup.get('type').valueChanges.subscribe((type: DatasourceType) => { datasourceFormGroup.get('entityAliasId').setValidators( (type === DatasourceType.entity || type === DatasourceType.entityCount) ? [Validators.required] : [] ); - dataKeysRequired = !this.modelValue.typeParameters || !this.modelValue.typeParameters.dataKeysOptional - || type === DatasourceType.entityCount; - datasourceFormGroup.get('dataKeys').setValidators(dataKeysRequired ? [Validators.required] : []); + const newDataKeysRequired = !this.dataKeysOptional(datasourceFormGroup.value); + datasourceFormGroup.get('dataKeys').setValidators(newDataKeysRequired ? [Validators.required] : []); datasourceFormGroup.get('entityAliasId').updateValueAndValidity(); datasourceFormGroup.get('dataKeys').updateValueAndValidity(); }); @@ -738,16 +752,19 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont let newDatasource: Datasource; if (this.functionsOnly) { newDatasource = deepClone(this.utils.getDefaultDatasource(this.modelValue.dataKeySettingsSchema.schema)); - newDatasource.dataKeys = [this.generateDataKey('Sin', DataKeyType.function)]; + newDatasource.dataKeys = [this.generateDataKey('Sin', DataKeyType.function, this.modelValue.dataKeySettingsSchema)]; } else { newDatasource = { type: DatasourceType.entity, dataKeys: [] }; } + if (this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) { + newDatasource.latestDataKeys = []; + } this.datasourcesFormArray().push(this.buildDatasourceForm(newDatasource)); } - public generateDataKey(chip: any, type: DataKeyType): DataKey { + public generateDataKey(chip: any, type: DataKeyType, datakeySettingsSchema: JsonSettingsSchema): DataKey { if (isObject(chip)) { (chip as DataKey)._hash = Math.random(); return chip; @@ -777,8 +794,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont } else if (type === DataKeyType.count) { result.name = 'count'; } - if (isDefined(this.modelValue.dataKeySettingsSchema.schema)) { - result.settings = this.utils.generateObjectFromJsonSchema(this.modelValue.dataKeySettingsSchema.schema); + if (datakeySettingsSchema && isDefined(datakeySettingsSchema.schema)) { + result.settings = this.utils.generateObjectFromJsonSchema(datakeySettingsSchema.schema); } return result; } @@ -793,14 +810,25 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont do { matches = false; datasources.forEach((datasource) => { - if (datasource && datasource.dataKeys) { - datasource.dataKeys.forEach((dataKey) => { - if (dataKey.label === label) { - i++; - label = name + ' ' + i; - matches = true; - } - }); + if (datasource) { + if (datasource.dataKeys) { + datasource.dataKeys.forEach((dataKey) => { + if (dataKey.label === label) { + i++; + label = name + ' ' + i; + matches = true; + } + }); + } + if (datasource.latestDataKeys) { + datasource.latestDataKeys.forEach((dataKey) => { + if (dataKey.label === label) { + i++; + label = name + ' ' + i; + matches = true; + } + }); + } } }); } while (matches); @@ -813,8 +841,9 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont const datasources = this.widgetType === widgetType.alarm ? [this.modelValue.config.alarmSource] : this.modelValue.config.datasources; if (datasources) { datasources.forEach((datasource) => { - if (datasource && datasource.dataKeys) { - i += datasource.dataKeys.length; + if (datasource && (datasource.dataKeys || datasource.latestDataKeys)) { + i += ((datasource.dataKeys ? datasource.dataKeys.length : 0) + + (datasource.latestDataKeys ? datasource.latestDataKeys.length : 0)); } }); } @@ -895,6 +924,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont } public validate(c: FormControl) { + this.dataError = ''; + this.datasourceError = []; if (!this.dataSettings.valid) { return { dataSettings: { @@ -945,6 +976,32 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont } }; } + if (this.widgetType === widgetType.timeseries && this.modelValue?.typeParameters?.hasAdditionalLatestDataKeys) { + let valid = config.datasources.filter(datasource => datasource?.dataKeys?.length).length > 0; + if (!valid) { + this.dataError = 'At least one timeseries data key should be specified'; + return { + timeseriesDataKeys: { + valid: false + } + }; + } else { + const emptyDatasources = config.datasources.filter(datasource => !datasource?.dataKeys?.length && + !datasource?.latestDataKeys?.length); + valid = emptyDatasources.length === 0; + if (!valid) { + for (const emptyDatasource of emptyDatasources) { + const i = config.datasources.indexOf(emptyDatasource); + this.datasourceError[i] = 'At least one data key should be specified'; + } + return { + dataKeys: { + valid: false + } + }; + } + } + } } } return null; 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 0506feb02d..da8fa8cbd0 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 @@ -162,6 +162,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI widgetSizeDetected = false; widgetInstanceInited = false; dataUpdatePending = false; + latestDataUpdatePending = false; pendingMessage: SubscriptionMessage; cafs: {[cafId: string]: CancelAnimationFrame} = {}; @@ -485,6 +486,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI if (!this.widgetTypeInstance.onDataUpdated) { this.widgetTypeInstance.onDataUpdated = () => {}; } + if (!this.widgetTypeInstance.onLatestDataUpdated) { + this.widgetTypeInstance.onLatestDataUpdated = () => {}; + } if (!this.widgetTypeInstance.onResize) { this.widgetTypeInstance.onResize = () => {}; } @@ -551,6 +555,10 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI }, 0); this.dataUpdatePending = false; } + if (this.latestDataUpdatePending) { + this.widgetTypeInstance.onLatestDataUpdated(); + this.latestDataUpdatePending = false; + } if (this.pendingMessage) { this.displayMessage(this.pendingMessage.severity, this.pendingMessage.message); this.pendingMessage = null; @@ -895,9 +903,23 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI } } catch (e){} }, + onLatestDataUpdated: () => { + try { + if (this.displayWidgetInstance()) { + if (this.widgetInstanceInited) { + this.widgetTypeInstance.onLatestDataUpdated(); + } else { + this.latestDataUpdatePending = true; + } + } + } catch (e){} + }, onDataUpdateError: (subscription, e) => { this.handleWidgetException(e); }, + onLatestDataUpdateError: (subscription, e) => { + this.handleWidgetException(e); + }, onSubscriptionMessage: (subscription, message) => { if (this.displayWidgetInstance()) { if (this.widgetInstanceInited) { @@ -972,6 +994,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI // backward compatibility this.widgetContext.datasources = subscription.datasources; this.widgetContext.data = subscription.data; + this.widgetContext.latestData = subscription.latestData; this.widgetContext.hiddenData = subscription.hiddenData; this.widgetContext.timeWindow = subscription.timeWindow; this.widgetContext.defaultSubscription = subscription; diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 4366ba0b63..5b6418c474 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -15,18 +15,16 @@ /// import { GridsterComponent, GridsterConfig, GridsterItem, GridsterItemComponentInterface } from 'angular-gridster2'; -import { Widget, WidgetPosition, widgetType } from '@app/shared/models/widget.models'; +import { FormattedData, Widget, WidgetPosition, widgetType } from '@app/shared/models/widget.models'; import { WidgetLayout, WidgetLayouts } from '@app/shared/models/dashboard.models'; import { IDashboardWidget, WidgetAction, WidgetContext, WidgetHeaderAction } from './widget-component.models'; import { Timewindow } from '@shared/models/time/time.models'; import { Observable, of, Subject } from 'rxjs'; -import { guid, isDefined, isEqual, isUndefined } from '@app/core/utils'; +import { formattedDataFormDatasourceData, guid, isDefined, isEqual, isUndefined } from '@app/core/utils'; import { IterableDiffer, KeyValueDiffer } from '@angular/core'; import { IAliasController, IStateController } from '@app/core/api/widget-api.models'; import { enumerable } from '@shared/decorators/enumerable'; import { UtilsService } from '@core/services/utils.service'; -import { FormattedData } from '@home/components/widget/lib/maps/map-models'; -import { parseData } from '@home/components/widget/lib/maps/common-maps-utils'; export interface WidgetsData { widgets: Array; @@ -456,7 +454,7 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { if (this.widgetContext.customHeaderActions) { let data: FormattedData[] = []; if (this.widgetContext.customHeaderActions.some(action => action.useShowWidgetHeaderActionFunction)) { - data = parseData(this.widgetContext.data || []); + data = formattedDataFormDatasourceData(this.widgetContext.data || []); } customHeaderActions = this.widgetContext.customHeaderActions.filter(action => this.filterCustomHeaderAction(action, data)); } else { 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 3bb727f4f5..d9f57c5202 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 @@ -18,7 +18,7 @@ import { IDashboardComponent } from '@home/models/dashboard-component.models'; import { DataSet, Datasource, - DatasourceData, + DatasourceData, FormattedData, JsonSettingsSchema, Widget, WidgetActionDescriptor, @@ -79,7 +79,6 @@ import { SortOrder } from '@shared/models/page/sort-order'; import { DomSanitizer } from '@angular/platform-browser'; import { Router } from '@angular/router'; import { catchError, map, mergeMap, switchMap } from 'rxjs/operators'; -import { FormattedData } from '@home/components/widget/lib/maps/map-models'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { EntityId } from '@shared/models/id/entity-id'; @@ -243,6 +242,7 @@ export class WidgetContext { datasources?: Array; data?: Array; + latestData?: Array; hiddenData?: Array<{data: DataSet}>; timeWindow?: WidgetTimewindow; @@ -410,6 +410,7 @@ export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescri alias: string; typeSettingsSchema?: string | any; typeDataKeySettingsSchema?: string | any; + typeLatestDataKeySettingsSchema?: string | any; image?: string; description?: string; componentFactory?: ComponentFactory; @@ -424,6 +425,7 @@ export interface WidgetConfigComponentData { isDataEnabled: boolean; settingsSchema: JsonSettingsSchema; dataKeySettingsSchema: JsonSettingsSchema; + latestDataKeySettingsSchema: JsonSettingsSchema; } export const MissingWidgetType: WidgetInfo = { @@ -478,12 +480,14 @@ export const ErrorWidgetType: WidgetInfo = { export interface WidgetTypeInstance { getSettingsSchema?: () => string; getDataKeySettingsSchema?: () => string; + getLatestDataKeySettingsSchema?: () => string; typeParameters?: () => WidgetTypeParameters; useCustomDatasources?: () => boolean; actionSources?: () => {[actionSourceId: string]: WidgetActionSource}; onInit?: () => void; onDataUpdated?: () => void; + onLatestDataUpdated?: () => void; onResize?: () => void; onEditModeChanged?: () => void; onMobileModeChanged?: () => void; @@ -510,6 +514,7 @@ export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo { controllerScript: widgetTypeEntity.descriptor.controllerScript, settingsSchema: widgetTypeEntity.descriptor.settingsSchema, dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema, + latestDataKeySettingsSchema: widgetTypeEntity.descriptor.latestDataKeySettingsSchema, defaultConfig: widgetTypeEntity.descriptor.defaultConfig }; } @@ -536,6 +541,7 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: controllerScript: widgetInfo.controllerScript, settingsSchema: widgetInfo.settingsSchema, dataKeySettingsSchema: widgetInfo.dataKeySettingsSchema, + latestDataKeySettingsSchema: widgetInfo.latestDataKeySettingsSchema, defaultConfig: widgetInfo.defaultConfig }; return { diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts index 005e3d18ad..c4dac29437 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-routing.module.ts @@ -39,7 +39,7 @@ const routes: Routes = [ path: '', component: EntitiesTableComponent, data: { - auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN], + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], title: 'asset.assets', assetsType: 'tenant' }, diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index 4f48c7d9d5..36899a657a 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -178,7 +178,8 @@ const routes: Routes = [ icon: 'domain' } as BreadCrumbConfig, auth: [Authority.TENANT_ADMIN], - title: 'customer.assets' + title: 'customer.assets', + assetsType: 'customer' }, resolve: { entitiesTableConfig: AssetsTableConfigResolver diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html index f3aa24f65c..9e912bf6b3 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html @@ -221,6 +221,22 @@
+ +
+
+ + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss index 222446d2ba..28f969b102 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.scss @@ -91,6 +91,13 @@ tb-widget-editor { width: 100%; height: 100%; } + + .mat-tab-label[aria-labelledby='hidden'] { + width: 0px !important; + min-width: 0px; + padding: 0px; + } + } .tb-split-vertical { diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts index 5221ca1d68..0ec6dbddf4 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -98,6 +98,9 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe @ViewChild('dataKeySettingsJsonInput', {static: true}) dataKeySettingsJsonInputElmRef: ElementRef; + @ViewChild('latestDataKeySettingsJsonInput', {static: true}) + latestDataKeySettingsJsonInputElmRef: ElementRef; + @ViewChild('javascriptInput', {static: true}) javascriptInputElmRef: ElementRef; @@ -126,6 +129,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe cssFullscreen = false; jsonSettingsFullscreen = false; jsonDataKeySettingsFullscreen = false; + jsonLatestDataKeySettingsFullscreen = false; javascriptFullscreen = false; iFrameFullscreen = false; @@ -135,6 +139,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe cssEditor: Ace.Editor; jsonSettingsEditor: Ace.Editor; dataKeyJsonSettingsEditor: Ace.Editor; + latestDataKeyJsonSettingsEditor: Ace.Editor; jsEditor: Ace.Editor; aceResize$: ResizeObserver; @@ -343,6 +348,19 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe }) )); + editorsObservables.push(this.createAceEditor(this.latestDataKeySettingsJsonInputElmRef, 'json').pipe( + tap((editor) => { + this.latestDataKeyJsonSettingsEditor = editor; + this.latestDataKeyJsonSettingsEditor.on('input', () => { + const editorValue = this.latestDataKeyJsonSettingsEditor.getValue(); + if (this.widget.latestDataKeySettingsSchema !== editorValue) { + this.widget.latestDataKeySettingsSchema = editorValue; + this.isDirty = true; + } + }); + }) + )); + editorsObservables.push(this.createAceEditor(this.javascriptInputElmRef, 'javascript').pipe( tap((editor) => { this.jsEditor = editor; @@ -373,6 +391,8 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe this.cssEditor.setValue(this.widget.templateCss ? this.widget.templateCss : '', -1); this.jsonSettingsEditor.setValue(this.widget.settingsSchema ? this.widget.settingsSchema : '', -1); this.dataKeyJsonSettingsEditor.setValue(this.widget.dataKeySettingsSchema ? this.widget.dataKeySettingsSchema : '', -1); + this.latestDataKeyJsonSettingsEditor.setValue(this.widget.latestDataKeySettingsSchema ? + this.widget.latestDataKeySettingsSchema : '', -1); this.jsEditor.setValue(this.widget.controllerScript ? this.widget.controllerScript : '', -1); } @@ -673,6 +693,19 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe ); } + beautifyLatestDataKeyJson(): void { + beautifyJs(this.widget.latestDataKeySettingsSchema, {indent_size: 4}).subscribe( + (res) => { + if (this.widget.latestDataKeySettingsSchema !== res) { + this.isDirty = true; + this.widget.latestDataKeySettingsSchema = res; + this.latestDataKeyJsonSettingsEditor.setValue(this.widget.latestDataKeySettingsSchema ? + this.widget.latestDataKeySettingsSchema : '', -1); + } + } + ); + } + beautifyJs(): void { beautifyJs(this.widget.controllerScript, {indent_size: 4, wrap_line_length: 60}).subscribe( (res) => { diff --git a/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts index 58e491b188..3079ff5476 100644 --- a/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts @@ -49,7 +49,7 @@ export class OtaPackageAutocompleteComponent implements ControlValueAccessor, On modelValue: string | EntityId | null; - private otaUpdateType: OtaUpdateType; + private otaUpdateType: OtaUpdateType = OtaUpdateType.FIRMWARE; get type(): OtaUpdateType { return this.otaUpdateType; diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index df28c3bcfc..f5fc25e69d 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -243,6 +243,7 @@ export interface DefaultDeviceProfileTransportConfiguration { export interface MqttDeviceProfileTransportConfiguration { deviceTelemetryTopic?: string; deviceAttributesTopic?: string; + sendAckOnValidationException?: boolean; transportPayloadTypeConfiguration?: { transportPayloadType?: TransportPayloadType; enableCompatibilityWithJsonPayloadFormat?: boolean; @@ -359,6 +360,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT const mqttTransportConfiguration: MqttDeviceProfileTransportConfiguration = { deviceTelemetryTopic: 'v1/devices/me/telemetry', deviceAttributesTopic: 'v1/devices/me/attributes', + sendAckOnValidationException: false, transportPayloadTypeConfiguration: { transportPayloadType: TransportPayloadType.JSON, enableCompatibilityWithJsonPayloadFormat: false, diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index 440a0b12ef..615d0994c9 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -15,7 +15,7 @@ /// import { ValidatorFn } from '@angular/forms'; -import { isNotEmptyStr } from '@core/utils'; +import { isNotEmptyStr, isNumber } from '@core/utils'; export const smtpPortPattern: RegExp = /^([0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; @@ -72,13 +72,15 @@ export const phoneNumberPatternTwilio = /^\+[1-9]\d{1,14}$|^(MG|PN).*$/; export enum SmsProviderType { AWS_SNS = 'AWS_SNS', - TWILIO = 'TWILIO' + TWILIO = 'TWILIO', + SMPP = 'SMPP' } export const smsProviderTypeTranslationMap = new Map( [ [SmsProviderType.AWS_SNS, 'admin.sms-provider-type-aws-sns'], - [SmsProviderType.TWILIO, 'admin.sms-provider-type-twilio'] + [SmsProviderType.TWILIO, 'admin.sms-provider-type-twilio'], + [SmsProviderType.SMPP, 'admin.sms-provider-type-smpp'] ] ); @@ -94,7 +96,216 @@ export interface TwilioSmsProviderConfiguration { numberFrom?: string; } -export type SmsProviderConfigurations = AwsSnsSmsProviderConfiguration & TwilioSmsProviderConfiguration; +export interface SmppSmsProviderConfiguration { + protocolVersion: number; + host: string; + port: number; + systemId: string; + password: string; + systemType?: string; + bindType?: string; + serviceType?: string; + sourceAddress?: string; + sourceTon?: number; + sourceNpi?: number; + destinationTon?: number; + destinationNpi?: number; + addressRange?: string; + codingScheme?: number; +} + +export const smppVersions = [ + {value: 3.3}, + {value: 3.4} +]; + +export enum BindTypes { + TX = 'TX', + RX = 'RX', + TRX = 'TRX' +} + +export const bindTypesTranslationMap = new Map([ + [BindTypes.TX, 'admin.smpp-provider.bind-type-tx'], + [BindTypes.RX, 'admin.smpp-provider.bind-type-rx'], + [BindTypes.TRX, 'admin.smpp-provider.bind-type-trx'] +]); + +export enum TypeOfNumber { + Unknown = 'Unknown', + International = 'International', + National = 'National', + NetworkSpecific = 'NetworkSpecific', + SubscriberNumber = 'SubscriberNumber', + Alphanumeric = 'Alphanumeric', + Abbreviated = 'Abbreviated' +} + +interface TypeDescriptor { + name: string; + value: number; +} + +export const typeOfNumberMap = new Map([ + [TypeOfNumber.Unknown, { + name: 'admin.smpp-provider.ton-unknown', + value: 0 + }], + [TypeOfNumber.International, { + name: 'admin.smpp-provider.ton-international', + value: 1 + }], + [TypeOfNumber.National, { + name: 'admin.smpp-provider.ton-national', + value: 2 + }], + [TypeOfNumber.NetworkSpecific, { + name: 'admin.smpp-provider.ton-network-specific', + value: 3 + }], + [TypeOfNumber.SubscriberNumber, { + name: 'admin.smpp-provider.ton-subscriber-number', + value: 4 + }], + [TypeOfNumber.Alphanumeric, { + name: 'admin.smpp-provider.ton-alphanumeric', + value: 5 + }], + [TypeOfNumber.Abbreviated, { + name: 'admin.smpp-provider.ton-abbreviated', + value: 6 + }], +]); + +export enum NumberingPlanIdentification { + Unknown = 'Unknown', + ISDN = 'ISDN', + DataNumberingPlan = 'DataNumberingPlan', + TelexNumberingPlan = 'TelexNumberingPlan', + LandMobile = 'LandMobile', + NationalNumberingPlan = 'NationalNumberingPlan', + PrivateNumberingPlan = 'PrivateNumberingPlan', + ERMESNumberingPlan = 'ERMESNumberingPlan', + Internet = 'Internet', + WAPClientId = 'WAPClientId', +} + +export const numberingPlanIdentificationMap = new Map([ + [NumberingPlanIdentification.Unknown, { + name: 'admin.smpp-provider.npi-unknown', + value: 0 + }], + [NumberingPlanIdentification.ISDN, { + name: 'admin.smpp-provider.npi-isdn', + value: 1 + }], + [NumberingPlanIdentification.DataNumberingPlan, { + name: 'admin.smpp-provider.npi-data-numbering-plan', + value: 3 + }], + [NumberingPlanIdentification.TelexNumberingPlan, { + name: 'admin.smpp-provider.npi-telex-numbering-plan', + value: 4 + }], + [NumberingPlanIdentification.LandMobile, { + name: 'admin.smpp-provider.npi-land-mobile', + value: 5 + }], + [NumberingPlanIdentification.NationalNumberingPlan, { + name: 'admin.smpp-provider.npi-national-numbering-plan', + value: 8 + }], + [NumberingPlanIdentification.PrivateNumberingPlan, { + name: 'admin.smpp-provider.npi-private-numbering-plan', + value: 9 + }], + [NumberingPlanIdentification.ERMESNumberingPlan, { + name: 'admin.smpp-provider.npi-ermes-numbering-plan', + value: 10 + }], + [NumberingPlanIdentification.Internet, { + name: 'admin.smpp-provider.npi-internet', + value: 13 + }], + [NumberingPlanIdentification.WAPClientId, { + name: 'admin.smpp-provider.npi-wap-client-id', + value: 18 + }], +]); + +export enum CodingSchemes { + SMSC = 'SMSC', + IA5 = 'IA5', + OctetUnspecified2 = 'OctetUnspecified2', + Latin1 = 'Latin1', + OctetUnspecified4 = 'OctetUnspecified4', + JIS = 'JIS', + Cyrillic = 'Cyrillic', + LatinHebrew = 'LatinHebrew', + UCS2UTF16 = 'UCS2UTF16', + PictogramEncoding = 'PictogramEncoding', + MusicCodes = 'MusicCodes', + ExtendedKanjiJIS = 'ExtendedKanjiJIS', + KoreanGraphicCharacterSet = 'KoreanGraphicCharacterSet', +} + +export const codingSchemesMap = new Map([ + [CodingSchemes.SMSC, { + name: 'admin.smpp-provider.scheme-smsc', + value: 0 + }], + [CodingSchemes.IA5, { + name: 'admin.smpp-provider.scheme-ia5', + value: 1 + }], + [CodingSchemes.OctetUnspecified2, { + name: 'admin.smpp-provider.scheme-octet-unspecified-2', + value: 2 + }], + [CodingSchemes.Latin1, { + name: 'admin.smpp-provider.scheme-latin-1', + value: 3 + }], + [CodingSchemes.OctetUnspecified4, { + name: 'admin.smpp-provider.scheme-octet-unspecified-4', + value: 4 + }], + [CodingSchemes.JIS, { + name: 'admin.smpp-provider.scheme-jis', + value: 5 + }], + [CodingSchemes.Cyrillic, { + name: 'admin.smpp-provider.scheme-cyrillic', + value: 6 + }], + [CodingSchemes.LatinHebrew, { + name: 'admin.smpp-provider.scheme-latin-hebrew', + value: 7 + }], + [CodingSchemes.UCS2UTF16, { + name: 'admin.smpp-provider.scheme-ucs-utf', + value: 8 + }], + [CodingSchemes.PictogramEncoding, { + name: 'admin.smpp-provider.scheme-pictogram-encoding', + value: 9 + }], + [CodingSchemes.MusicCodes, { + name: 'admin.smpp-provider.scheme-music-codes', + value: 10 + }], + [CodingSchemes.ExtendedKanjiJIS, { + name: 'admin.smpp-provider.scheme-extended-kanji-jis', + value: 13 + }], + [CodingSchemes.KoreanGraphicCharacterSet, { + name: 'admin.smpp-provider.scheme-korean-graphic-character-set', + value: 14 + }], +]); + +export type SmsProviderConfigurations = + Partial & AwsSnsSmsProviderConfiguration & TwilioSmsProviderConfiguration; export interface SmsProviderConfiguration extends SmsProviderConfigurations { type: SmsProviderType; @@ -118,6 +329,11 @@ export function smsProviderConfigurationValidator(required: boolean): ValidatorF valid = isNotEmptyStr(twilioConfiguration.numberFrom) && isNotEmptyStr(twilioConfiguration.accountSid) && isNotEmptyStr(twilioConfiguration.accountToken); break; + case SmsProviderType.SMPP: + const smppConfiguration = configuration as SmppSmsProviderConfiguration; + valid = isNotEmptyStr(smppConfiguration.host) && isNumber(smppConfiguration.port) + && isNotEmptyStr(smppConfiguration.systemId) && isNotEmptyStr(smppConfiguration.password); + break; } } if (!valid) { @@ -156,6 +372,26 @@ export function createSmsProviderConfiguration(type: SmsProviderType): SmsProvid }; smsProviderConfiguration = {...twilioSmsProviderConfiguration, type: SmsProviderType.TWILIO}; break; + case SmsProviderType.SMPP: + const smppSmsProviderConfiguration: SmppSmsProviderConfiguration = { + protocolVersion: 3.3, + host: '', + port: null, + systemId: '', + password: '', + systemType: '', + bindType: 'TX', + serviceType: '', + sourceAddress: '', + sourceTon: 5, + sourceNpi: 0, + destinationTon: 5, + destinationNpi: 0, + addressRange: '', + codingScheme: 0 + }; + smsProviderConfiguration = {...smppSmsProviderConfiguration, type: SmsProviderType.SMPP}; + break; } } return smsProviderConfiguration; diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 703cffcaee..b45017fdf2 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -143,6 +143,7 @@ export interface WidgetTypeDescriptor { controllerScript: string; settingsSchema?: string | any; dataKeySettingsSchema?: string | any; + latestDataKeySettingsSchema?: string | any; defaultConfig: string; sizeX: number; sizeY: number; @@ -157,6 +158,7 @@ export interface WidgetTypeParameters { stateData?: boolean; hasDataPageLink?: boolean; singleEntity?: boolean; + hasAdditionalLatestDataKeys?: boolean; warnOnPageDataOverflow?: boolean; ignoreDataUpdateOnIntervalTick?: boolean; } @@ -165,6 +167,7 @@ export interface WidgetControllerDescriptor { widgetTypeFunction?: any; settingsSchema?: string | any; dataKeySettingsSchema?: string | any; + latestDataKeySettingsSchema?: string | any; typeParameters?: WidgetTypeParameters; actionSources?: {[actionSourceId: string]: WidgetActionSource}; } @@ -282,6 +285,7 @@ export interface Datasource { name?: string; aliasName?: string; dataKeys?: Array; + latestDataKeys?: Array; entityType?: EntityType; entityId?: string; entityName?: string; @@ -299,9 +303,31 @@ export interface Datasource { keyFilters?: Array; entityFilter?: EntityFilter; dataKeyStartIndex?: number; + latestDataKeyStartIndex?: number; [key: string]: any; } +export interface FormattedData { + $datasource: Datasource; + entityName: string; + deviceName: string; + entityId: string; + entityType: EntityType; + entityLabel: string; + entityDescription: string; + aliasName: string; + dsIndex: number; + dsName: string; + deviceType: string; + [key: string]: any; +} + +export interface ReplaceInfo { + variable: string; + valDec?: number; + dataKeyName: string; +} + export type DataSet = [number, any][]; export interface DataSetHolder { 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 5c73d7a017..2588a4d410 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -112,6 +112,7 @@ "sms-provider-type-required": "SMS provider type is required.", "sms-provider-type-aws-sns": "Amazon SNS", "sms-provider-type-twilio": "Twilio", + "sms-provider-type-smpp": "SMPP", "aws-access-key-id": "AWS Access Key ID", "aws-access-key-id-required": "AWS Access Key ID is required", "aws-secret-access-key": "AWS Secret Access Key", @@ -253,6 +254,64 @@ "all-platforms": "All platforms", "allowed-platforms": "Allowed platforms" }, + "smpp-provider": { + "smpp-version": "SMPP version", + "smpp-host": "SMPP host", + "smpp-host-required": "SMPP host is required", + "smpp-port": "SMPP port", + "smpp-port-required": "SMPP port is required", + "system-id": "System ID", + "system-id-required": "System ID is required", + "password": "Password", + "password-required": "Password is required", + "type-settings": "Type settings", + "source-settings": "Source settings", + "destination-settings": "Destination settings", + "additional-settings": "Additional settings", + "system-type": "System type", + "bind-type": "Bind type", + "service-type": "Service type", + "source-address": "Source address", + "source-ton": "Source TON", + "source-npi": "Source NPI", + "destination-ton": "Destination TON (Type of Number)", + "destination-npi": "Destination NPI (Numbering Plan Identification)", + "address-range": "Address range", + "coding-scheme": "Coding scheme", + "bind-type-tx": "Transmitter", + "bind-type-rx": "Receiver", + "bind-type-trx": "Transciever", + "ton-unknown": "Unknown", + "ton-international": "International", + "ton-national": "National", + "ton-network-specific": "Network Specific", + "ton-subscriber-number": "Subscriber Number", + "ton-alphanumeric": "Alphanumeric", + "ton-abbreviated": "Abbreviated", + "npi-unknown": "0 - Unknown", + "npi-isdn": "1 - ISDN/telephone numbering plan (E163/E164)", + "npi-data-numbering-plan": "3 - Data numbering plan (X.121)", + "npi-telex-numbering-plan": "4 - Telex numbering plan (F.69)", + "npi-land-mobile": "6 - Land Mobile (E.212)", + "npi-national-numbering-plan": "8 - National numbering plan", + "npi-private-numbering-plan": "9 - Private numbering plan", + "npi-ermes-numbering-plan": "10 - ERMES numbering plan (ETSI DE/PS 3 01-3)", + "npi-internet": "13 - Internet (IP)", + "npi-wap-client-id": "18 - WAP Client Id (to be defined by WAP Forum)", + "scheme-smsc": "0 - SMSC Default Alphabet (ASCII for short and long code and to GSM for toll-free)", + "scheme-ia5": "1 - IA5 (ASCII for short and long code, Latin 9 for toll-free (ISO-8859-9))", + "scheme-octet-unspecified-2": "2 - Octet Unspecified (8-bit binary)", + "scheme-latin-1": "3 - Latin 1 (ISO-8859-1)", + "scheme-octet-unspecified-4": "4 - Octet Unspecified (8-bit binary)", + "scheme-jis": "5 - JIS (X 0208-1990)", + "scheme-cyrillic": "6 - Cyrillic (ISO-8859-5)", + "scheme-latin-hebrew": "7 - Latin/Hebrew (ISO-8859-8)", + "scheme-ucs-utf": "8 - UCS2/UTF-16 (ISO/IEC-10646)", + "scheme-pictogram-encoding": "9 - Pictogram Encoding", + "scheme-music-codes": "10 - Music Codes (ISO-2022-JP)", + "scheme-extended-kanji-jis": "13 - Extended Kanji JIS (X 0212-1990)", + "scheme-korean-graphic-character-set": "14 - Korean Graphic Character Set (KS C 5601/KS X 1001)" + }, "queue-select-name": "Select queue name", "queue-name": "Name", "queue-name-required": "Queue name is required!", @@ -898,7 +957,20 @@ "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", "alarm-fields-required": "Alarm fields are required.", "function-types": "Function types", + "function-type": "Function type", "function-types-required": "Function types are required.", + "alarm-keys": "Alarm data keys", + "alarm-key": "Alarm data key", + "alarm-key-functions": "Alarm key functions", + "alarm-key-function": "Alarm key function", + "latest-keys": "Latest data keys", + "latest-key": "Latest data key", + "latest-key-functions": "Latest key functions", + "latest-key-function": "Latest key function", + "timeseries-keys": "Timeseries data keys", + "timeseries-key": "Timeseries data key", + "timeseries-key-functions": "Timeseries key functions", + "timeseries-key-function": "Timeseries key function", "maximum-function-types": "Maximum { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", "time-description": "timestamp of the current value;", "value-description": "the current value;", @@ -1127,9 +1199,9 @@ "mobile-dashboard-hint": "Used by mobile application as a device details dashboard", "select-queue-hint": "Select from a drop-down list.", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", - "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", + "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data including associated OTA updates will become unrecoverable.", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", - "delete-device-profiles-text": "Be careful, after the confirmation all selected device profiles will be removed and all related data will become unrecoverable.", + "delete-device-profiles-text": "Be careful, after the confirmation all selected device profiles will be removed and all related data including associated OTA updates will become unrecoverable.", "set-default-device-profile-title": "Are you sure you want to make the device profile '{{deviceProfileName}}' default?", "set-default-device-profile-text": "After the confirmation the device profile will be marked as default and will be used for new devices with no profile specified.", "no-device-profiles-found": "No device profiles found.", @@ -1143,6 +1215,8 @@ "mqtt-enable-compatibility-with-json-payload-format-hint": "When enabled, the platform will use a Protobuf payload format by default. If parsing fails, the platform will attempt to use JSON payload format. Useful for backward compatibility during firmware updates. For example, the initial release of the firmware uses Json, while the new release uses Protobuf. During the process of firmware update for the fleet of devices, it is required to support both Protobuf and JSON simultaneously. The compatibility mode introduces slight performance degradation, so it is recommended to disable this mode once all devices are updated.", "mqtt-use-json-format-for-default-downlink-topics": "Use Json format for default downlink topics", "mqtt-use-json-format-for-default-downlink-topics-hint": "When enabled, the platform will use Json payload format to push attributes and RPC via the following topics: v1/devices/me/attributes/response/$request_id, v1/devices/me/attributes, v1/devices/me/rpc/request/$request_id, v1/devices/me/rpc/response/$request_id. This setting does not impact attribute and rpc subscriptions sent using new (v2) topics: v2/a/res/$request_id, v2/a, v2/r/req/$request_id, v2/r/res/$request_id. Where $request_id is an integer request identifier.", + "mqtt-send-ack-on-validation-exception": "Send PUBACK on PUBLISH message validation failure", + "mqtt-send-ack-on-validation-exception-hint": "By default, the platform will close the MQTT session on message validation failure. When enabled, the platform will send publish acknowledgment instead of closing the session.", "snmp-add-mapping": "Add SNMP mapping", "snmp-mapping-not-configured": "No mapping for OID to timeseries/telemetry configured", "snmp-timseries-or-attribute-name": "Timeseries/attribute name for mapping", @@ -3039,6 +3113,7 @@ "css": "CSS", "settings-schema": "Settings schema", "datakey-settings-schema": "Data key settings schema", + "latest-datakey-settings-schema": "Latest data key settings schema", "widget-settings": "Widget settings", "description": "Description", "image-preview": "Image preview", 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 51d08e8939..2a6127f672 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ru_RU.json +++ b/ui-ngx/src/assets/locale/locale.constant-ru_RU.json @@ -107,8 +107,33 @@ "general-policy": "Общая политика", "max-failed-login-attempts": "Максимальное количество неудачных попыток входа в систему, прежде чем учетная запись заблокирована", "minimum-max-failed-login-attempts-range": "Максимальное количество неудачных попыток входа в систему не может быть отрицательным", - "user-lockout-notification-email": "В случае блокировки учетной записи пользователя отправьте уведомление на электронную почту" - }, + "user-lockout-notification-email": "В случае блокировки учетной записи пользователя отправьте уведомление на электронную почту", + "smpp-provider": { + "smpp-version": "SMPP версия", + "smpp-host": "SMPP хост", + "smpp-host-required": "SMPP хост обязателен.", + "smpp-port": "SMPP порт", + "smpp-port-required": "SMPP порт обязателен.", + "system-id": "ИД системи", + "system-id-required": "ИД системи обязателен.", + "password": "Пароль", + "password-required": "Пароль обязателен.", + "type-settings": "Настройки типов", + "source-settings": "Настройки источника", + "destination-settings": "Настройки назначения", + "additional-settings": "Дополнительные настройки", + "system-type": "Тип системы", + "bind-type": "Тип привязки", + "service-type": "Тип обслуживания", + "source-address": "Адрес источника", + "source-ton": "Тип номера источника", + "source-npi": "Идентификация плана нумерации источника", + "destination-ton": "Тип номера назничения", + "destination-npi": "Идентификация плана нумерации назначения", + "address-range": "Диапазон адресов", + "coding-scheme": "Схема кодирования" + } + }, "alarm": { "alarm": "Оповещение", "alarms": "Оповещения", 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 003c7881ca..91039b302b 100644 --- a/ui-ngx/src/assets/locale/locale.constant-uk_UA.json +++ b/ui-ngx/src/assets/locale/locale.constant-uk_UA.json @@ -123,7 +123,32 @@ "general-policy": "Загальна політика", "max-failed-login-attempts": "Максимальна кількість невдалих спроб входу, перш ніж обліковий запис заблоковано", "minimum-max-failed-login-attempts-range": "Максимальна кількість невдалих спроб входу не може бути негативною", - "user-lockout-notification-email": "У разі блокування облікового запису користувача, надішліть сповіщення на електронну пошту" + "user-lockout-notification-email": "У разі блокування облікового запису користувача, надішліть сповіщення на електронну пошту", + "smpp-provider": { + "smpp-version": "SMPP верія", + "smpp-host": "SMPP хост", + "smpp-host-required": "Хост SMPP обов'язковий.", + "smpp-port": "SMPP порт", + "smpp-port-required": "Порт SMPP обов'язковий.", + "system-id": "Id системи", + "system-id-required": "Id системи обязателен.", + "password": "Пароль", + "password-required": "Пароль обязателен.", + "type-settings": "Налаштування типів", + "source-settings": "Налаштування джерела", + "destination-settings": "Налаштування призначення", + "additional-settings": "Додаткові налаштування", + "system-type": "Тип системи", + "bind-type": "Тип зв'язування", + "service-type": "Тип обслуговування", + "source-address": "Адреса джерела", + "source-ton": "Тип номера джерела", + "source-npi": "Идентификация плана нумерации джерела", + "destination-ton": "Тип номера призначення", + "destination-npi": "Ідентифікація плану нумерації призначення", + "address-range": "Діапазон адрес", + "coding-scheme": "Схема кодування" + } }, "alarm": { "alarm": "Сигнал тривоги", 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 8746872f5a..11e34c9102 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -249,8 +249,8 @@ "created-time": "创建时间", "details": "详情", "display-status": { - "ACTIVE_ACK": "Active 已确认", - "ACTIVE_UNACK": "Active 未确认", + "ACTIVE_ACK": "激活已确认", + "ACTIVE_UNACK": "激活未确认", "CLEARED_ACK": "清除已确认", "CLEARED_UNACK": "清除未确认" }, @@ -265,15 +265,15 @@ "no-alarms-matching": "没有找到匹配 '{{entity}}' 的警告", "no-alarms-prompt": "未发现警告", "no-data": "无数据显示", - "originator": "Originator", - "originator-type": "Originator 类型", + "originator": "发起者", + "originator-type": "发起者类型", "polling-interval": "警告轮询间隔(秒)", "polling-interval-required": "警告轮询间隔必填。", "search": "查找警告", "search-propagated-alarms": "检索已传递的警报", "search-status": { "ACK": "已确认", - "ACTIVE": "Active", + "ACTIVE": "激活", "ANY": "所有", "CLEARED": "已清除", "UNACK": "未确认" @@ -540,9 +540,11 @@ "enter-password": "输入密码", "enter-search": "输入检索条件", "enter-username": "输入用户名", - "loading": "Loading...", + "loading": "正在加载中...", "password": "密码", - "username": "用户名" + "username": "用户名", + "proceed": "继续", + "open-details-page": "打开详情页" }, "confirm-on-exit": { "html-message": "您有未保存的更改。
确定要离开此页面吗?", @@ -687,6 +689,7 @@ "display-filters": "显示筛选器", "display-title": "显示仪表板标题", "drop-image": "拖拽图像或单击以选择要上传的文件。", + "maximum-upload-file-size": "最大上传文件大小: {{ size }}", "edit-state": "仪表板状态编辑", "export": "导出仪表板", "export-failed-error": "无法导出仪表板: {{error}}", @@ -762,6 +765,10 @@ "state-name-required": "仪表板状态名必填。", "states": "仪表板状态", "title": "标题", + "image": "仪表板图片", + "mobile-app-settings": "移动应用设置", + "mobile-order": "移动应用中的仪表板订单", + "mobile-hide": "在移动应用中隐藏仪表板", "title-color": "标题颜色", "title-required": "标题必填。", "toolbar-always-open": "工具栏常驻", @@ -887,6 +894,8 @@ "create-new-device-profile": "创建一个新的!", "default": "默认", "default-rule-chain": "默认规则链", + "mobile-dashboard": "移动仪表板", + "mobile-dashboard-hint": "被移动应用用作设备详情仪表板", "delete": "删除设备配置", "delete-device-profile-text": "请注意:确认后设备配置和所有相关数据将不可恢复。", "delete-device-profile-title": "是否确实要删除设备配置 '{{deviceProfileName}}'?", @@ -995,7 +1004,8 @@ "transport-type-snmp-hint": "指定 SNMP 传输配置", "type": "配置类型", "type-default": "默认", - "type-required": "配置类型必填。" + "type-required": "配置类型必填。", + "image": "设备配置图片" }, "device": { "access-token": "访问令牌", @@ -1997,7 +2007,11 @@ "change-password": "更改密码", "current-password": "当前密码", "last-login-time": "最后登录", - "profile": "属性" + "profile": "属性", + "copy-jwt-token": "复制 JWT 令牌", + "valid-till": "有效期至 {{expirationData}}", + "tokenCopiedSuccessMessage": "JWT 令牌已复制到剪贴板", + "tokenCopiedWarnMessage": "JWT 令牌已过期!请刷新页面。" }, "queue": { "name": "队列名称", diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 4f75b24d78..0aac92b131 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -4541,13 +4541,13 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== -"flot.curvedlines@git://github.com/MichaelZinsmaier/CurvedLines.git#master": +"flot.curvedlines@https://github.com/MichaelZinsmaier/CurvedLines.git#master": version "1.1.1" - resolved "git://github.com/MichaelZinsmaier/CurvedLines.git#22ed1fc2a6ccafc816c2d07b36027cc123825c4b" + resolved "https://github.com/MichaelZinsmaier/CurvedLines.git#22ed1fc2a6ccafc816c2d07b36027cc123825c4b" -"flot@git://github.com/thingsboard/flot.git#0.9-work": +"flot@https://github.com/thingsboard/flot.git#0.9-work": version "0.9.0-alpha" - resolved "git://github.com/thingsboard/flot.git#0ff0c775db7c74e705f6c3c2bba92080a202ccd4" + resolved "https://github.com/thingsboard/flot.git#0ff0c775db7c74e705f6c3c2bba92080a202ccd4" follow-redirects@^1.0.0: version "1.14.5" @@ -6637,9 +6637,9 @@ ngx-drag-drop@^2.0.0: dependencies: tslib "^1.9.0" -"ngx-flowchart@git://github.com/thingsboard/ngx-flowchart.git#release/1.0.0": +"ngx-flowchart@https://github.com/thingsboard/ngx-flowchart.git#release/1.0.0": version "0.0.1" - resolved "git://github.com/thingsboard/ngx-flowchart.git#aac96f7e0490a386d58864d7819873e7c63cbb48" + resolved "https://github.com/thingsboard/ngx-flowchart.git#aac96f7e0490a386d58864d7819873e7c63cbb48" dependencies: tslib "^2.2.0"