diff --git a/application/src/main/data/json/demo/dashboards/firmware.json b/application/src/main/data/json/demo/dashboards/firmware.json index 6e8d18cd98..cfd0ed68c4 100644 --- a/application/src/main/data/json/demo/dashboards/firmware.json +++ b/application/src/main/data/json/demo/dashboards/firmware.json @@ -1,5 +1,6 @@ { "title": "Firmware", + "image": null, "configuration": { "description": "", "widgets": { @@ -222,6 +223,27 @@ "funcBody": null, "usePostProcessing": null, "postFuncBody": null + }, + { + "name": "fw_url", + "type": "attribute", + "label": "fw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.4204673738685043, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ] } @@ -247,24 +269,24 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", - "customCss": "", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" }, { - "name": "Download firware", + "name": "Download firmware", "icon": "file_download", "type": "custom", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "12533058-42f6-e75f-620c-219c48d01ec0" }, { - "name": "Copy checksum", + "name": "Copy checksum/URL", "icon": "content_copy", "type": "custom", - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" } ] @@ -996,6 +1018,27 @@ "funcBody": null, "usePostProcessing": null, "postFuncBody": null + }, + { + "name": "fw_url", + "type": "attribute", + "label": "fw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.4204673738685043, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ] } @@ -1021,24 +1064,24 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", - "customCss": "", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" }, { - "name": "Download firware", + "name": "Download firmware", "icon": "file_download", "type": "custom", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "12533058-42f6-e75f-620c-219c48d01ec0" }, { - "name": "Copy checksum", + "name": "Copy checksum/URL", "icon": "content_copy", "type": "custom", - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" } ] @@ -1272,6 +1315,27 @@ "funcBody": null, "usePostProcessing": null, "postFuncBody": null + }, + { + "name": "fw_url", + "type": "attribute", + "label": "fw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.4204673738685043, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ] } @@ -1297,24 +1361,24 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", - "customCss": "", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" }, { - "name": "Download firware", + "name": "Download firmware", "icon": "file_download", "type": "custom", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "12533058-42f6-e75f-620c-219c48d01ec0" }, { - "name": "Copy checksum", + "name": "Copy checksum/URL", "icon": "content_copy", "type": "custom", - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" } ] @@ -1548,6 +1612,27 @@ "funcBody": null, "usePostProcessing": null, "postFuncBody": null + }, + { + "name": "fw_url", + "type": "attribute", + "label": "fw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.4204673738685043, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ] } @@ -1573,24 +1658,24 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", - "customCss": "", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" }, { - "name": "Download firware", + "name": "Download firmware", "icon": "file_download", "type": "custom", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "12533058-42f6-e75f-620c-219c48d01ec0" }, { - "name": "Copy checksum", + "name": "Copy checksum/URL", "icon": "content_copy", "type": "custom", - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" } ] @@ -1824,6 +1909,27 @@ "funcBody": null, "usePostProcessing": null, "postFuncBody": null + }, + { + "name": "fw_url", + "type": "attribute", + "label": "fw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.4204673738685043, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null } ] } @@ -1849,24 +1955,24 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", - "customCss": "", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" }, { - "name": "Download firware", + "name": "Download firmware", "icon": "file_download", "type": "custom", - "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "12533058-42f6-e75f-620c-219c48d01ec0" }, { - "name": "Copy checksum", + "name": "Copy checksum/URL", "icon": "content_copy", "type": "custom", - "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Firmware direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n }\n}", "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" } ] @@ -1935,7 +2041,7 @@ } }, "device_firmware_history": { - "name": "Device Firmware history", + "name": "Firmware history: ${entityName}", "root": false, "layouts": { "main": { @@ -2378,7 +2484,8 @@ "titleColor": "rgba(0,0,0,0.870588)", "showFilters": true, "showDashboardLogo": false, - "dashboardLogoUrl": null + "dashboardLogoUrl": null, + "showUpdateDashboardImage": false } }, "name": "Firmware" diff --git a/application/src/main/data/json/demo/dashboards/software.json b/application/src/main/data/json/demo/dashboards/software.json new file mode 100644 index 0000000000..84316fa5fd --- /dev/null +++ b/application/src/main/data/json/demo/dashboards/software.json @@ -0,0 +1,2492 @@ +{ + "title": "Software", + "image": null, + "configuration": { + "description": "", + "widgets": { + "cd03188e-cd9d-9601-fd57-da4cb95fc016": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "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": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": false, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "8fdb88d0-50ac-2232-fdb7-69c30c16544e", + "dataKeys": [ + { + "name": "current_sw_title", + "type": "timeseries", + "label": "Current SW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_sw_version", + "type": "timeseries", + "label": "Current SW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_title", + "type": "timeseries", + "label": "Target SW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_version", + "type": "timeseries", + "label": "Target SW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_ts", + "type": "timeseries", + "label": "Target SW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_checksum", + "type": "attribute", + "label": "sw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_url", + "type": "attribute", + "label": "sw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.3355829384124256, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History software update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_software_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit software", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download software", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum/URL", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016" + }, + "100b756c-0082-6505-3ae1-3603e6deea48": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "timeseries_table", + "type": "timeseries", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 8, + "sizeY": 6.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "19f41c21-d9af-e666-8f50-e1748778f955", + "filterId": null, + "dataKeys": [ + { + "name": "current_sw_title", + "type": "timeseries", + "label": "Current software title", + "color": "#2196f3", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.5978079905579401, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_sw_version", + "type": "timeseries", + "label": "Current software version", + "color": "#4caf50", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.027392025058568192, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_title", + "type": "timeseries", + "label": "Target software title", + "color": "#f44336", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.9496350796287059, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_version", + "type": "timeseries", + "label": "Target software version", + "color": "#ffc107", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.6734152252264187, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Status", + "color": "#607d8b", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.2983399718643074, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "function capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\nif (value !== '') {\n return capitalize(value);\n}\nreturn value;" + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 2592000000, + "quickInterval": "CURRENT_DAY", + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 200 + } + }, + "showTitle": false, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "showTimestamp": true, + "displayPagination": true, + "defaultPageSize": 10, + "enableSearch": true, + "enableStickyHeader": true, + "enableStickyAction": true + }, + "title": "Software history", + "dropShadow": false, + "enableFullscreen": false, + "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", + "displayTimewindow": true, + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "100b756c-0082-6505-3ae1-3603e6deea48" + }, + "17543c57-af4a-2c1e-bf12-53a7b46791e6": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "waitingDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n \n
\n ${waitingDevicesNumber:0}\n
\n
\n Device Waiting\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_waiting", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "4d9a77a2-f0a5-690c-a83b-b0e940be788c" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6" + }, + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "updatingDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n \n
\n ${updatingDevicesNumber:0}\n
\n
\n Device Updating\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_updating", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "57d39904-2350-b29b-78ed-56b8268814cb" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6" + }, + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "6044e198-df64-cd76-f339-696f220c4943", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "updatedDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n \n
\n ${updatedDevicesNumber:0}\n
\n
\n Device Updated\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_updated", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "d787c212-8c56-34f0-349a-5aae2ffd1eae" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81" + }, + "77b10144-b904-edd5-8c7c-8fb75616c6d8": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "updatingDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n
\n \n \n \n
\n
\n ${updatingDevicesNumber:0}\n
\n \n
\n Device Failed\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .container-svg {\n height: 40px;\n width: 40px;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .container-svg {\n height: 28px;\n width: 28px;\n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_error", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "0b3d2887-9929-84d5-3795-0763dca15cba" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8" + }, + "21be08bb-ec90-f760-ad6f-e7678f12c401": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "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": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", + "dataKeys": [ + { + "name": "current_sw_title", + "type": "timeseries", + "label": "Current SW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_sw_version", + "type": "timeseries", + "label": "Current SW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_title", + "type": "timeseries", + "label": "Target SW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_version", + "type": "timeseries", + "label": "Target SW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_ts", + "type": "timeseries", + "label": "Target SW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_checksum", + "type": "attribute", + "label": "sw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_url", + "type": "attribute", + "label": "sw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.3355829384124256, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History software update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_software_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit software", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download software", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum/URL", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "21be08bb-ec90-f760-ad6f-e7678f12c401" + }, + "e8280043-d3dc-7acb-c2ff-a4522972ff91": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "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": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897", + "dataKeys": [ + { + "name": "current_sw_title", + "type": "timeseries", + "label": "Current SW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_sw_version", + "type": "timeseries", + "label": "Current SW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_title", + "type": "timeseries", + "label": "Target SW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_version", + "type": "timeseries", + "label": "Target SW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_ts", + "type": "timeseries", + "label": "Target SW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_checksum", + "type": "attribute", + "label": "sw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_url", + "type": "attribute", + "label": "sw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.3355829384124256, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History software update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_software_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit software", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download software", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum/URL", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91" + }, + "3624013b-378c-f110-5eba-ae95c25a4dcc": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "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": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", + "dataKeys": [ + { + "name": "current_sw_title", + "type": "timeseries", + "label": "Current SW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_sw_version", + "type": "timeseries", + "label": "Current SW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_title", + "type": "timeseries", + "label": "Target SW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_version", + "type": "timeseries", + "label": "Target SW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_ts", + "type": "timeseries", + "label": "Target SW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_checksum", + "type": "attribute", + "label": "sw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_url", + "type": "attribute", + "label": "sw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.3355829384124256, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History software update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_software_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit software", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download software", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum/URL", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "3624013b-378c-f110-5eba-ae95c25a4dcc" + }, + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "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": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "6044e198-df64-cd76-f339-696f220c4943", + "dataKeys": [ + { + "name": "current_sw_title", + "type": "timeseries", + "label": "Current SW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_sw_version", + "type": "timeseries", + "label": "Current SW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_title", + "type": "timeseries", + "label": "Target SW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_version", + "type": "timeseries", + "label": "Target SW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_sw_ts", + "type": "timeseries", + "label": "Target SW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_checksum", + "type": "attribute", + "label": "sw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "sw_url", + "type": "attribute", + "label": "sw_url", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.3355829384124256, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History software update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_software_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit software", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit software {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "form {\n min-width: 300px !important;\n}", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n softwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n softwareId: vm.entity.softwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.softwareId = formValues.softwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download software", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet otaPackageService = $injector.get(widgetContext.servicesMap.get('otaPackageService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceSoftware();\n\nfunction getDeviceSoftware() {\n var entityIdValue = entityId.id;\n var data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url === '') {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.softwareId !== null) {\n otaPackageService.downloadOtaPackage(data.softwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.softwareId !== null) {\n otaPackageService.downloadOtaPackage(deviceProfile.softwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n });\n }\n }\n );\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum/URL", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_checksum');\nvar checksum = data.data[0][1];\nconsole.log(checksum);\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Software checksum has been copied to clipboard', 2000, 'top');\n} else {\n data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'sw_url');\n var url = data.data[0][1];\n if (url !== '') {\n copyToClipboard(url);\n widgetContext.showSuccessToast('Software direct URL has been copied to clipboard', 2000, 'top');\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not software set.', 2000, 'top');\n }\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176" + } + }, + "states": { + "default": { + "name": "Device list", + "root": true, + "layouts": { + "main": { + "widgets": { + "cd03188e-cd9d-9601-fd57-da4cb95fc016": { + "sizeX": 19, + "sizeY": 12, + "row": 0, + "col": 0 + }, + "17543c57-af4a-2c1e-bf12-53a7b46791e6": { + "sizeX": 5, + "sizeY": 3, + "row": 0, + "col": 19 + }, + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { + "sizeX": 5, + "sizeY": 3, + "row": 3, + "col": 19 + }, + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { + "sizeX": 5, + "sizeY": 3, + "row": 9, + "col": 19 + }, + "77b10144-b904-edd5-8c7c-8fb75616c6d8": { + "sizeX": 5, + "sizeY": 3, + "row": 6, + "col": 19 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 12, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": true, + "mobileRowHeight": 70 + } + } + } + }, + "device_software_history": { + "name": "Software history: ${entityName}", + "root": false, + "layouts": { + "main": { + "widgets": { + "100b756c-0082-6505-3ae1-3603e6deea48": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_waiting": { + "name": "Device waiting", + "root": false, + "layouts": { + "main": { + "widgets": { + "21be08bb-ec90-f760-ad6f-e7678f12c401": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_updating": { + "name": "Device updating", + "root": false, + "layouts": { + "main": { + "widgets": { + "e8280043-d3dc-7acb-c2ff-a4522972ff91": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_updated": { + "name": "Device updated", + "root": false, + "layouts": { + "main": { + "widgets": { + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { + "sizeX": 27, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_error": { + "name": "Device failed", + "root": false, + "layouts": { + "main": { + "widgets": { + "3624013b-378c-f110-5eba-ae95c25a4dcc": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + } + }, + "entityAliases": { + "639da5b4-31f0-0151-6282-c37a3897b7e8": { + "id": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "alias": "All devices", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "DEVICE" + } + }, + "19f41c21-d9af-e666-8f50-e1748778f955": { + "id": "19f41c21-d9af-e666-8f50-e1748778f955", + "alias": "State entity", + "filter": { + "type": "stateEntity", + "resolveMultiple": false, + "stateEntityParamName": null, + "defaultStateEntity": null + } + } + }, + "filters": { + "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e": { + "id": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", + "filter": "WaitingDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "sw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "QUEUED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "579f0468-9ce9-7e3e-b34c-88dd3de59897": { + "id": "579f0468-9ce9-7e3e-b34c-88dd3de59897", + "filter": "UpdatingDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "sw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "OR", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "INITIATED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "sw_state equel", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "DOWNLOADING", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "sw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "DOWNLOADED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "sw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "VERIFIED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "sw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "UPDATING", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "sw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + } + ], + "type": "COMPLEX" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "6044e198-df64-cd76-f339-696f220c4943": { + "id": "6044e198-df64-cd76-f339-696f220c4943", + "filter": "UpdetedDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "sw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "UPDATED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "bdbc6ea1-95a7-3912-341a-58dc7704a00f": { + "id": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", + "filter": "FailedDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "sw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "FAILED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "8fdb88d0-50ac-2232-fdb7-69c30c16544e": { + "id": "8fdb88d0-50ac-2232-fdb7-69c30c16544e", + "filter": "DeviceSearch", + "keyFilters": [ + { + "key": { + "type": "ENTITY_FIELD", + "key": "name" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "CONTAINS", + "value": { + "defaultValue": "" + }, + "ignoreCase": true, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "Device name", + "autogeneratedLabel": false, + "order": 0 + } + } + ] + } + ], + "editable": true + } + }, + "timewindow": { + "displayValue": "", + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1618998609030, + "endTimeMs": 1619085009030 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "settings": { + "stateControllerId": "entity", + "showTitle": false, + "showDashboardsSelect": false, + "showEntitiesSelect": false, + "showDashboardTimewindow": true, + "showDashboardExport": false, + "toolbarAlwaysOpen": true, + "titleColor": "rgba(0,0,0,0.870588)", + "showFilters": true, + "showDashboardLogo": false, + "dashboardLogoUrl": null, + "showUpdateDashboardImage": false + } + }, + "name": "Software" +} \ No newline at end of file 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 29b8b9f8d3..79f732bca8 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -28,7 +28,7 @@ "alias": "html_card", "name": "HTML Card", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAAAAABslHx1AAAAAmJLR0QA/4ePzL8AAATFSURBVHja7d3tUxNXFMdx/u7vEkAJsKQK6bilKaBV1Eh5GGNaSyq0WodhRuqMtQJqW5UHCwKGMRJiQpL99cWGEGaaTjtjgaTnvNpz7mYnn8neu/duXtwWFbdWlxs8Vrf21VJcy5TU4FHKrBVbtjJqgshstayWmgFSWm1ZVlPEskEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAG+T9ACrlcLjjyc7lcWflcbfhBfnByOZfL5cpSKZfL+f/2G+WAJ/8dJAHh4GgFeK4BaiOjMYDtyskzAIvSQ+BtI0JmKif3NRBk3PM8Lwwhz/M8LxtAzgf30RoNBJEkjUOk0j4GwJok6XaDQ8LwtSSVXVrP1of4v1w978bu5YNh4fHlfnfwfpDo15Hevm/eHkCyP8Tc/rGV44d8FaLbl/QCRrrqQopXg251bkdSdjBIIpuSNAtAbwXyqgsA595Hh3Sm0+l0Or1QDzJxLWiYhCftdSFJoKMHiPnyvwTOhIHeD9KGA3SfIYBkOsGJtIPz/GNDauIvIfEFGJdKnZwtOPUgOw4kfT0E1vQMmPb12IEZaRJCz1RKBpBJCG+ocAVixw25XArTUdACTBbqdvY56MhLisK8xisDXRyiUi+MStoDnqjUCT9Keg3Oh48MCcXj8Xg8PlwPMqQE/Kzr8HK3LiQBFyozhaI8uClJ8+AUi8CD6qi1DYymUqkksH7Mnf0zLcNIvo2In64LGYXhauLCHUlaAnb3gIUq5FXN7//qmCH98s8RmoXv9KYuZBI+ryZ9cFsKzsvngUdVyAowcDGIjWOGRKRpaINNrdWF3IUeX9LS/PymLsMlSZqCLikM01XIe+CnE3ogRqRtAE9arQtZCVqynbCoWXBWpfddMCaNgLsnLQSj1gVwM5Je3/RPAKIBYPYIJOp5nud5dw8uFIPQxO1eCOf1oRs6Jm71QOtG0FMiydFQAHkKnJlIjTgkTwIyB87uEUglbh5c6G13UGhdkvSyPUicB5J0HYBQe/Bkv+tUPnr/JCDZVq7obyF6d6MNnC+COdTmSCs4A78Hs5dkG3zyYqAy13oec8CJ/XZ6l7qFzfW9apJ/80dNsr5V2yNyGxt7tmY3iEEMYhCDGOTEIJnD6YtBDGKQpocsDrvR5K4kyX96PeoOzu5Lysbj8b2Zvv7SkepphgwBcD4vqRAPVlXR91IauASUVAhWgny6e7ohtEdCwcpdCaDTBYYDCED5sHrxdEOuFZRx4Ya06cC3ZS05sKI04EzM3demA1NlLTqweuo7ewI86U7lHdYQzCgNzEnSHXB9SYPw/amHpKBPGoFzqVQqFYUxpYEX0pHqeINAav4wvXoI+eyweq1BIDHorry+nT6E1FRnGgQyCtFqUxVyo7baGJBHlVG4mFivgRxU9xMbjQIpR4HBqYRLz84hpBwFhqYSLu5Og0C07VZ6dXTvEHKk2iAQZZOdQHcqX3NrSdlb1WrjLHXL26/T/j+s2prdIAYxiEEMYhCDGMQgBjGIQQxiEIMYxCAGMYhBDGIQgxjEIAYxiEEMYhCDGMQgBjGIQQxiEIMY5PRAmmaD4ObYsvndVst+c2yiXWppjm3NS/oTe0OjFEeU1MMAAAAASUVORK5CYII=", - "description": "Useful to inject custom HTML code. Designed to displays static information only.", + "description": "Useful to inject custom HTML code. Designed to display static information only.", "descriptor": { "type": "static", "sizeX": 7.5, diff --git a/application/src/main/data/json/system/widget_bundles/control_widgets.json b/application/src/main/data/json/system/widget_bundles/control_widgets.json index 8fea056506..4756b149c3 100644 --- a/application/src/main/data/json/system/widget_bundles/control_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/control_widgets.json @@ -10,16 +10,16 @@ "alias": "rpc_debug_terminal", "name": "RPC debug terminal", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAWcklEQVR42u2dB5QWRZeGi6QgklFUDJhFMCJGRFHMomMAMStiBvUXRTFgRjGLERMoiIBZ1GVXdNcF4yIe5agYdvVnfkAwIAoiIs4+/72na3u+NN8wAzsD73vmzOnur7rCrbfuvV3VfSuUlZXNnDnzmGOOadKkSRCEKgAKlZSUzJgxA1IFWNWyZUsJRagutGjRAlIFdJVkIVQvevbsGWQBhWpH06ZNJQRBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEITlRK9evc5KwAdAnTt3rlevXvz1oIMOir+efvrp3bp1a9WqVUYOderUOfDAAy+77LJLL7308MMPX2ONNYop99577+ULyaOPPrpwsvfff59kG264Ya0W8uDBg6+55pqq5PDRRx8hh3XXXbfWtPnzzz8vK4/PPvtsjz328F9ffvnljF9///33q6++Ot6+/vrrv/fee+kEX3zxxeabb15biHXqqafecsstK5S4a6655pIlS/7444+11lprtSPWEUcc0alTp/333//hhx/mdNasWa54nFj9+vXj19122+2MM8747rvvuHLiiSe6rnrrrbc4feyxxzp27Aifbr/9dk6nTp1aW4j10ksvkf8uu+yyQoVM/piC1VFjbbbZZtGu+RUXhBMLgxjTQymuvPrqqxzvs88+HH/44Ydps/jJJ59wccstt8wua+ONN7722mvvuOOOQw89NJtYlAIv7777buwp+aSJtd122w0YMIBbUDDRUjMYsL8bbbSRn+63336ctm/f3k/XXnvtiy66iFsw4htssAE/MWzSlWncuDEXUc/kT7L4cXn9+vVxD+65557bbrttr732iunJ7dxzz91mm22o4QEHHECIA27v0aMHgkKFDxs2jIsk23PPPWng0KFD995773jv3wx+3Ldv34svvrh58+a06L777jv77LMbNGiQtgBcf+CBB8hz6623XnWIBd59990CxOrSpQtX3n77bY6HDBnCMfJNZ4gfRl9uuummGQWhz3788UfS//nnn/z/4Ycf0sR6/PHHOcVkLF26lAMkmybW3LlzMSVuaseOHes/jRkzhlPI7aeuLI8//niOGzVq5PxetmzZX3/9VVpayvFdd92Vrs86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", - "description": "Allows to send any RPC command using it's name and parameters to device. Useful for debug.", + "description": "Allows to send any RPC command using its name and parameters to device. Useful for debug.", "descriptor": { "type": "rpc", "sizeX": 9.5, "sizeY": 5.5, "resources": [], "templateHtml": "
", - "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n", - "controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var utils = self.ctx.$scope.$injector.get(self.ctx.servicesMap.get('utils'));\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = utils.guid();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\n }\n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n \n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n \nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\n \"type\": \"boolean\",\n \"default\": false\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\"\n ]\n}", + "templateCss": ".cmd .cursor.blink {\n -webkit-animation-name: terminal-underline;\n -moz-animation-name: terminal-underline;\n -ms-animation-name: terminal-underline;\n animation-name: terminal-underline;\n}\n.terminal .inverted, .cmd .inverted {\n border-bottom-color: #aaa;\n}\n\n", + "controllerScript": "var requestTimeout = 500;\nvar multiParams = false;\nvar useRowStyleFunction = false;\nvar styleObj = {};\n\nself.onInit = function() {\n var subscription = self.ctx.defaultSubscription;\n var rpcEnabled = subscription.rpcEnabled;\n var deviceName = 'Simulated';\n var prompt;\n if (subscription.targetDeviceName && subscription.targetDeviceName.length) {\n deviceName = subscription.targetDeviceName;\n }\n if (self.ctx.settings.requestTimeout) {\n requestTimeout = self.ctx.settings.requestTimeout;\n }\n if (self.ctx.settings.multiParams) {\n multiParams = self.ctx.settings.multiParams;\n }\n if (self.ctx.settings.useRowStyleFunction && self.ctx.settings.rowStyleFunction) {\n try {\n var style = self.ctx.settings.rowStyleFunction;\n styleObj = JSON.parse(style);\n if ((typeof styleObj !== \"object\")) {\n styleObj = null;\n throw new URIError(`${style === null ? 'null' : typeof style} instead of style object`);\n }\n else if (typeof styleObj === \"object\" && (typeof styleObj.length) === \"number\") {\n styleObj = null;\n throw new URIError('Array instead of style object');\n }\n }\n catch (e) {\n console.log(`Row style function in widget ` +\n `returns '${e}'. Please check your row style function.`); \n }\n useRowStyleFunction = self.ctx.settings.useRowStyleFunction;\n \n }\n var greetings = 'Welcome to ThingsBoard RPC debug terminal.\\n\\n';\n if (!rpcEnabled) {\n greetings += 'Target device is not set!\\n\\n';\n prompt = '';\n } else {\n greetings += 'Current target device for RPC commands: [[b;#fff;]' + deviceName + ']\\n\\n';\n greetings += 'Please type [[b;#fff;]\\'help\\'] to see usage.\\n';\n prompt = '[[b;#8bc34a;]' + deviceName +']> ';\n }\n \n var terminal = $('#device-terminal', self.ctx.$container).terminal(\n function(command) {\n if (command !== '') {\n try {\n var localCommand = command.trim();\n var requestUUID = uuidv4();\n if (localCommand === 'help') {\n printUsage(this);\n } else {\n var cmdObj = $.terminal.parse_command(localCommand);\n if (cmdObj.args) {\n if (!multiParams && cmdObj.args.length > 1) {\n this.error(\"Wrong number of arguments!\");\n this.echo(' ');\n }\n else {\n if (cmdObj.args.length) {\n var params = getMultiParams(cmdObj.args);\n }\n performRpc(this, cmdObj.name, params, requestUUID);\n }\n }\n \n }\n } catch(e) {\n this.error(new String(e));\n }\n } else {\n this.echo('');\n }\n }, {\n greetings: greetings,\n prompt: prompt,\n enabled: rpcEnabled\n });\n \n if (styleObj && styleObj !== null) {\n terminal.css(styleObj);\n }\n \n if (!rpcEnabled) {\n terminal.error('No RPC target detected!').pause();\n }\n}\n\n\nfunction printUsage(terminal) {\n var commandsListText = '\\n[[b;#fff;]Usage:]\\n';\n commandsListText += ' [params body]]\\n\\n';\n commandsListText += '[[b;#fff;]Example 1 (multiParams===false):]\\n'; \n commandsListText += ' myRemoteMethod1 myText\\n\\n'; \n commandsListText += '[[b;#fff;]Example 2 (multiParams===false):]\\n'; \n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\":2,\\\\\"key2\\\\\":\\\\\"myVal\\\\\"}\"\\n\\n'; \n commandsListText += '[[b;#fff;]Example 3 (multiParams===true)]\\n'; \n commandsListText += ' [params body] = \"all the string after the method, including spaces\"]\\n';\n commandsListText += ' myOtherRemoteMethod \"{\\\\\"key1\\\\\": \"battery level\", \\\\\"key2\\\\\": \\\\\"myVal\\\\\"}\"\\n'; \n terminal.echo(new String(commandsListText));\n}\n\nfunction performRpc(terminal, method, params, requestUUID) {\n terminal.pause();\n self.ctx.controlApi.sendTwoWayCommand(method, params, requestTimeout, requestUUID).subscribe(\n function success(responseBody) {\n terminal.echo(JSON.stringify(responseBody));\n terminal.echo(' ');\n terminal.resume();\n },\n function fail() {\n var errorText = self.ctx.defaultSubscription.rpcErrorText;\n terminal.error(errorText);\n terminal.echo(' ');\n terminal.resume();\n }\n );\n}\n\nfunction getMultiParams(cmdObj) {\n var params = \"\";\n cmdObj.forEach((element) => {\n try {\n params += \" \" + JSON.strigify(JSON.parse(element));\n } catch (e) {\n params += \" \" + element;\n }\n })\n return params.trim();\n}\n\n\nfunction uuidv4() {\n return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {\n var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);\n return v.toString(16);\n });\n}\n\n \nself.onDestroy = function() {\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"properties\": {\n \"requestTimeout\": {\n \"title\": \"RPC request timeout (ms)\",\n \"type\": \"number\",\n \"default\": 500\n },\n \"multiParams\": {\n \"title\": \"RPC params All line\",\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(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": [\"requestTimeout\"]\n },\n \"form\": [\n \"requestTimeout\",\n \"multiParams\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"targetDeviceAliases\":[],\"showTitle\":true,\"backgroundColor\":\"#010101\",\"color\":\"rgba(255, 254, 254, 0.87)\",\"padding\":\"0px\",\"settings\":{\"parseGpioStatusFunction\":\"return body[pin] === true;\",\"gpioStatusChangeRequest\":{\"method\":\"setGpioStatus\",\"paramsBody\":\"{\\n \\\"pin\\\": \\\"{$pin}\\\",\\n \\\"enabled\\\": \\\"{$enabled}\\\"\\n}\"},\"requestTimeout\":500,\"switchPanelBackgroundColor\":\"#b71c1c\",\"gpioStatusRequest\":{\"method\":\"getGpioStatus\",\"paramsBody\":\"{}\"},\"gpioList\":[{\"pin\":1,\"label\":\"GPIO 1\",\"row\":0,\"col\":0,\"_uniqueKey\":0},{\"pin\":2,\"label\":\"GPIO 2\",\"row\":0,\"col\":1,\"_uniqueKey\":1},{\"pin\":3,\"label\":\"GPIO 3\",\"row\":1,\"col\":0,\"_uniqueKey\":2}]},\"title\":\"RPC debug terminal\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } @@ -28,7 +28,7 @@ "alias": "rpc_remote_shell", "name": "RPC remote shell", "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAACgCAIAAADGnbT+AAAABmJLR0QA/wD/AP+gvaeTAAAU6klEQVR42u2dCbSW0xrHtyZDOpVkyFCKIplCZpLMIVJIhsgYmSUakLniInOGyFRXaCn3ilws89Q1RrpRSq4k15Tx3N96Ht9e73m/4Xzn9EVH//8666z97u99997v3v/9PM8e3meH8vLyWbNmdevWrUGDBkEQFgNQqGvXrtOmTYNUAVatssoqqhShVGjcuDGkCsgq1YVQWnTv3j1IAwolR1lZmSpBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEARBEAShmujRo8fxGfAB0NZbb127du3465577hl/7d2796677tqkSZNUCsstt9wee+zRv3//c845p0uXLvXq1ftLVtQ222zDJ50PP/xwlZ7abbfdeOruu+8mXKtWLcKzZ89eJoj1/vvvl1fEe++9t9122/mvEyZMSP26aNGiQYMGxcfXXHPNl156KXnDBx980KpVq6Xh1aD7FVdcsdVWW4lYfxqx9t9//y233JJauPXWW7mcM2eOCx4n1imnnMKv1Oyxxx47b948Yg4//HCXVc888wyXt99+e7t27eDT8OHDuXzttdeWhlcbOnQohTnmmGNErD+NWC1btox6zWPQiZFYKMR4P5QiZuLEiYR32WUXwq+//npSLb711ltEbrDBBqmMDjnkENTluuuue+mllx533HEe2axZs/POO++mm24666yzVl11VY9s27Ytd3bo0GHvvfe+/PLLhw0btummmxJ/0EEHXXfddRdddFGbNm2SKcPpCy+8kET69evXqFEjj6QzPPXUU5Rk3LhxJ554Yry5c+fOJHjttdfisIDS5qyTpk2bnn322eR1/vnnR+kbiUV2Q4YMufLKK7fffvvkU+uss86AAQMoxhlnnBEdIIhYLWPMiy++WIBYO+64IzHPP/884csuu4wwJEgmiB2GQbbeeuulMpo8eTI3z5w5k/8kS8xOO+20cOFCLr/77jv+z507t3nz5sT37NnTpSb/f/75Z/5///33Dz74IIGffvrJ74/pQ5pffvmFyB9++MGbbf311yceLxcew81Tp071m2l1T8TTueOOO7IrZOONN/7qq6/IlxTQ+9y57777RmKRPsl6qX799VdeNhLom2++ie+CH4S11lpLxPqd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", - "description": "Allows to send emulate remote shell. Requires custom implementation on the target device to work properly.", + "description": "Allows to emulate remote shell. Requires custom implementation on the target device to work properly.", "descriptor": { "type": "rpc", "sizeX": 9.5, diff --git a/application/src/main/data/json/system/widget_bundles/digital_gauges.json b/application/src/main/data/json/system/widget_bundles/digital_gauges.json index 755ac603b0..9455ac4100 100644 --- a/application/src/main/data/json/system/widget_bundles/digital_gauges.json +++ b/application/src/main/data/json/system/widget_bundles/digital_gauges.json @@ -100,7 +100,7 @@ "alias": "lcd_bar_gauge", "name": "LCD bar gauge", "image": "data:image/png;base64,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", - "description": "Preconfigured gauge to display any value reading as an bar. Allows to configure value range, gradient colors and other settings.", + "description": "Preconfigured gauge to display any value reading as a bar. Allows to configure value range, gradient colors and other settings.", "descriptor": { "type": "latest", "sizeX": 2, 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 410e70714b..74260d3021 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 @@ -10,7 +10,7 @@ "alias": "device_admin_table", "name": "Device admin table", "image": "data:image/png;base64,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", - "description": "Customized entity table widget with preconfigured actions to create update and delete devices.", + "description": "Customized entity table widget with preconfigured actions to create, update and delete devices.", "descriptor": { "type": "latest", "sizeX": 7.5, @@ -28,7 +28,7 @@ "alias": "asset_admin_table", "name": "Asset admin table", "image": "data:image/png;base64,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", - "description": "Customized entity table widget with preconfigured actions to create update and delete assets.", + "description": "Customized entity table widget with preconfigured actions to create, update and delete assets.", "descriptor": { "type": "latest", "sizeX": 7.5, diff --git a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json index 313dd52323..bec97fe3cf 100644 --- a/application/src/main/data/json/system/widget_bundles/gateway_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/gateway_widgets.json @@ -10,7 +10,7 @@ "alias": "gateway_configuration", "name": "Gateway Configuration", "image": "data:image/png;base64,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", - "description": "Allows to define widget gateway configuration for a single selected device.", + "description": "Allows to define gateway configuration for a single selected device.", "descriptor": { "type": "static", "sizeX": 8, diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 8a80aa80f1..689452dbf7 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -28,7 +28,7 @@ "alias": "update_multiple_attributes", "name": "Update Multiple Attributes", "image": "data:image/png;base64,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", - "description": "Allows to create an input form and set values for multiple attributes simultaniously.", + "description": "Allows to create an input form and set values for multiple attributes simultaneously.", "descriptor": { "type": "latest", "sizeX": 7.5, 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 aaf958a646..41d5ff55b2 100644 --- a/application/src/main/data/json/system/widget_bundles/maps.json +++ b/application/src/main/data/json/system/widget_bundles/maps.json @@ -46,7 +46,7 @@ "alias": "route_map", "name": "Route Map", "image": "data:image/png;base64,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", - "description": "TTrip animation on the Google maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", + "description": "Trip animation on the Google maps. Allows to visualize location change over time. Use Trip Animation widget for advanced features.", "descriptor": { "type": "timeseries", "sizeX": 8.5, diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index 4fec49130a..9e94caf1af 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -59,14 +59,15 @@ CREATE TABLE IF NOT EXISTS resource ( CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key) ); -CREATE TABLE IF NOT EXISTS firmware ( - id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS ota_package ( + id uuid NOT NULL CONSTRAINT ota_package_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, device_profile_id uuid, type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, + url varchar(255), file_name varchar(255), content_type varchar(255), checksum_algorithm varchar(32), @@ -75,7 +76,68 @@ CREATE TABLE IF NOT EXISTS firmware ( data_size bigint, additional_info varchar, search_text varchar(255), - CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) + CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) +); + +CREATE TABLE IF NOT EXISTS oauth2_params ( + id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY, + enabled boolean, + tenant_id uuid, + created_time bigint NOT NULL +); + +CREATE TABLE IF NOT EXISTS oauth2_registration ( + id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, + created_time bigint NOT NULL, + additional_info varchar, + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + custom_send_token boolean, + CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE +); + +CREATE TABLE IF NOT EXISTS oauth2_domain ( + id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, + CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme) +); + +CREATE TABLE IF NOT EXISTS oauth2_mobile ( + id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, + created_time bigint NOT NULL, + pkg_name varchar(255), + callback_url_scheme varchar(255), + CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, + CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name) ); ALTER TABLE dashboard @@ -101,13 +163,13 @@ DO $$ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN ALTER TABLE device_profile ADD CONSTRAINT fk_firmware_device_profile - FOREIGN KEY (firmware_id) REFERENCES firmware(id); + FOREIGN KEY (firmware_id) REFERENCES ota_package(id); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN ALTER TABLE device_profile ADD CONSTRAINT fk_software_device_profile - FOREIGN KEY (firmware_id) REFERENCES firmware(id); + FOREIGN KEY (firmware_id) REFERENCES ota_package(id); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_dashboard_device_profile') THEN @@ -119,13 +181,13 @@ DO $$ IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN ALTER TABLE device ADD CONSTRAINT fk_firmware_device - FOREIGN KEY (firmware_id) REFERENCES firmware(id); + FOREIGN KEY (firmware_id) REFERENCES ota_package(id); END IF; IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN ALTER TABLE device ADD CONSTRAINT fk_software_device - FOREIGN KEY (firmware_id) REFERENCES firmware(id); + FOREIGN KEY (firmware_id) REFERENCES ota_package(id); 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 c54f940c38..b52f85af92 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleNodeStateService; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -311,6 +313,14 @@ public class ActorSystemContext { @Autowired(required = false) @Getter private EdgeRpcService edgeRpcService; + @Lazy + @Autowired(required = false) + @Getter private ResourceService resourceService; + + @Lazy + @Autowired(required = false) + @Getter private OtaPackageService otaPackageService; + @Value("${actors.session.max_concurrent_sessions_per_device:1}") @Getter private long maxConcurrentSessionsPerDevice; 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 5af11d87f5..747a16a5e6 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 @@ -192,6 +192,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { syncSessionSet.add(key); } }); + log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions); syncSessionSet.forEach(rpcSubscriptions::remove); } @@ -454,7 +455,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } else { SessionInfoMetaData sessionMD = sessions.get(sessionId); if (sessionMD == null) { - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); } sessionMD.setSubscribedToAttributes(true); log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); @@ -475,7 +476,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } else { SessionInfoMetaData sessionMD = sessions.get(sessionId); if (sessionMD == null) { - sessionMD = new SessionInfoMetaData(new SessionInfo(SessionType.SYNC, sessionInfo.getNodeId())); + sessionMD = new SessionInfoMetaData(new SessionInfo(subscribeCmd.getSessionType(), sessionInfo.getNodeId())); } sessionMD.setSubscribedToRPC(true); log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 1ddb99d7b7..9a1afb9ff8 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.TbResultSetFuture; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -486,6 +488,16 @@ class DefaultTbContext implements TbContext { return mainCtx.getEntityViewService(); } + @Override + public ResourceService getResourceService() { + return mainCtx.getResourceService(); + } + + @Override + public OtaPackageService getOtaPackageService() { + return mainCtx.getOtaPackageService(); + } + @Override public RuleEngineDeviceProfileCache getDeviceProfileCache() { return mainCtx.getDeviceProfileCache(); diff --git a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java index bbcb7d656a..bc93253a73 100644 --- a/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java +++ b/application/src/main/java/org/thingsboard/server/config/CustomOAuth2AuthorizationRequestResolver.java @@ -37,6 +37,8 @@ import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponentsBuilder; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; +import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.service.security.auth.oauth2.TbOAuth2ParameterNames; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; @@ -46,12 +48,13 @@ import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.HashMap; import java.util.Map; +import java.util.UUID; @Service @Slf4j public class CustomOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { - public static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; - public static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/"; + private static final String DEFAULT_AUTHORIZATION_REQUEST_BASE_URI = "/oauth2/authorization"; + private static final String DEFAULT_LOGIN_PROCESSING_URI = "/login/oauth2/code/"; private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; private static final char PATH_DELIMITER = '/'; @@ -63,6 +66,9 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza @Autowired private ClientRegistrationRepository clientRegistrationRepository; + @Autowired + private OAuth2Service oAuth2Service; + @Autowired(required = false) private OAuth2Configuration oauth2Configuration; @@ -71,7 +77,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { String registrationId = this.resolveRegistrationId(request); String redirectUriAction = getAction(request, "login"); - return resolve(request, registrationId, redirectUriAction); + String appPackage = getAppPackage(request); + return resolve(request, registrationId, redirectUriAction, appPackage); } @Override @@ -80,7 +87,8 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza return null; } String redirectUriAction = getAction(request, "authorize"); - return resolve(request, registrationId, redirectUriAction); + String appPackage = getAppPackage(request); + return resolve(request, registrationId, redirectUriAction, appPackage); } private String getAction(HttpServletRequest request, String defaultAction) { @@ -91,8 +99,12 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza return action; } + private String getAppPackage(HttpServletRequest request) { + return request.getParameter("pkg"); + } + @SuppressWarnings("deprecation") - private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction) { + private OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId, String redirectUriAction, String appPackage) { if (registrationId == null) { return null; } @@ -104,6 +116,14 @@ public class CustomOAuth2AuthorizationRequestResolver implements OAuth2Authoriza Map attributes = new HashMap<>(); attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId()); + if (!StringUtils.isEmpty(appPackage)) { + String callbackUrlScheme = this.oAuth2Service.findCallbackUrlScheme(UUID.fromString(registrationId), appPackage); + if (StringUtils.isEmpty(callbackUrlScheme)) { + throw new IllegalArgumentException("Invalid package: " + appPackage + ". No package info found for Client Registration."); + } else { + attributes.put(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME, callbackUrlScheme); + } + } OAuth2AuthorizationRequest.Builder builder; if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) { diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 03883fc167..f0af59c7bc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -110,8 +110,11 @@ public class AlarmController extends BaseController { checkParameter(ALARM_ID, strAlarmId); try { AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - checkAlarmId(alarmId, Operation.WRITE); + Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); + logEntityAction(alarm.getOriginator(), alarm, + getCurrentUser().getCustomerId(), + ActionType.ALARM_DELETE, null); sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED); return alarmService.deleteAlarm(getTenantId(), alarmId); 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 e6384c010d..7d41deb2ab 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -17,7 +17,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Getter; import lombok.extern.slf4j.Slf4j; @@ -31,7 +30,6 @@ import org.springframework.web.bind.annotation.ExceptionHandler; 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.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; @@ -39,8 +37,8 @@ import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.TbResourceInfo; @@ -70,7 +68,7 @@ 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.EntityViewId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -79,10 +77,6 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -94,9 +88,6 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; -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.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; @@ -110,7 +101,7 @@ import org.thingsboard.server.dao.edge.EdgeService; 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.firmware.FirmwareService; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.oauth2.OAuth2Service; @@ -127,8 +118,9 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.action.RuleEngineEntityActionService; import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.service.firmware.FirmwareStateService; +import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.rpc.EdgeGrpcService; import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService; @@ -147,11 +139,9 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletResponse; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -250,10 +240,10 @@ public abstract class BaseController { protected TbResourceService resourceService; @Autowired - protected FirmwareService firmwareService; + protected OtaPackageService otaPackageService; @Autowired - protected FirmwareStateService firmwareStateService; + protected OtaPackageStateService otaPackageStateService; @Autowired protected TbQueueProducerProvider producerProvider; @@ -279,6 +269,9 @@ public abstract class BaseController { @Autowired(required = false) protected EdgeGrpcService edgeGrpcService; + @Autowired + protected RuleEngineEntityActionService ruleEngineEntityActionService; + @Value("${server.log_controller_error_stack_trace}") @Getter private boolean logControllerErrorStackTrace; @@ -511,8 +504,8 @@ public abstract class BaseController { case TB_RESOURCE: checkResourceId(new TbResourceId(entityId.getId()), operation); return; - case FIRMWARE: - checkFirmwareId(new FirmwareId(entityId.getId()), operation); + case OTA_PACKAGE: + checkOtaPackageId(new OtaPackageId(entityId.getId()), operation); return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); @@ -769,25 +762,25 @@ public abstract class BaseController { } } - Firmware checkFirmwareId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { + OtaPackage checkOtaPackageId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { try { - validateId(firmwareId, "Incorrect firmwareId " + firmwareId); - Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId); - checkNotNull(firmware); - accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmware); - return firmware; + validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); + OtaPackage otaPackage = otaPackageService.findOtaPackageById(getCurrentUser().getTenantId(), otaPackageId); + checkNotNull(otaPackage); + accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackage); + return otaPackage; } catch (Exception e) { throw handleException(e, false); } } - FirmwareInfo checkFirmwareInfoId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { + OtaPackageInfo checkOtaPackageInfoId(OtaPackageId otaPackageId, Operation operation) throws ThingsboardException { try { - validateId(firmwareId, "Incorrect firmwareId " + firmwareId); - FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(getCurrentUser().getTenantId(), firmwareId); - checkNotNull(firmwareInfo); - accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmwareInfo); - return firmwareInfo; + validateId(otaPackageId, "Incorrect otaPackageId " + otaPackageId); + OtaPackageInfo otaPackageIn = otaPackageService.findOtaPackageInfoById(getCurrentUser().getTenantId(), otaPackageId); + checkNotNull(otaPackageIn); + accessControlService.checkPermission(getCurrentUser(), Resource.OTA_PACKAGE, operation, otaPackageId, otaPackageIn); + return otaPackageIn; } catch (Exception e) { throw handleException(e, false); } @@ -809,7 +802,7 @@ public abstract class BaseController { customerId = user.getCustomerId(); } if (e == null) { - pushEntityActionToRuleEngine(entityId, entity, user, customerId, actionType, additionalInfo); + ruleEngineEntityActionService.pushEntityActionToRuleEngine(entityId, entity, user.getTenantId(), customerId, actionType, user, additionalInfo); } auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); } @@ -819,184 +812,6 @@ public abstract class BaseController { return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; } - private void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId, - ActionType actionType, Object... additionalInfo) { - String msgType = null; - switch (actionType) { - case ADDED: - msgType = DataConstants.ENTITY_CREATED; - break; - case DELETED: - msgType = DataConstants.ENTITY_DELETED; - break; - case UPDATED: - msgType = DataConstants.ENTITY_UPDATED; - break; - case ASSIGNED_TO_CUSTOMER: - msgType = DataConstants.ENTITY_ASSIGNED; - break; - case UNASSIGNED_FROM_CUSTOMER: - msgType = DataConstants.ENTITY_UNASSIGNED; - break; - case ATTRIBUTES_UPDATED: - msgType = DataConstants.ATTRIBUTES_UPDATED; - break; - case ATTRIBUTES_DELETED: - msgType = DataConstants.ATTRIBUTES_DELETED; - break; - case ALARM_ACK: - msgType = DataConstants.ALARM_ACK; - break; - case ALARM_CLEAR: - msgType = DataConstants.ALARM_CLEAR; - break; - case ASSIGNED_FROM_TENANT: - msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT; - break; - case ASSIGNED_TO_TENANT: - msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; - break; - case PROVISION_SUCCESS: - msgType = DataConstants.PROVISION_SUCCESS; - break; - case PROVISION_FAILURE: - msgType = DataConstants.PROVISION_FAILURE; - break; - case TIMESERIES_UPDATED: - msgType = DataConstants.TIMESERIES_UPDATED; - break; - case TIMESERIES_DELETED: - msgType = DataConstants.TIMESERIES_DELETED; - break; - case ASSIGNED_TO_EDGE: - msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE; - break; - case UNASSIGNED_FROM_EDGE: - msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE; - break; - } - if (!StringUtils.isEmpty(msgType)) { - try { - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("userId", user.getId().toString()); - metaData.putValue("userName", user.getName()); - if (customerId != null && !customerId.isNullUid()) { - metaData.putValue("customerId", customerId.toString()); - } - if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) { - String strCustomerId = extractParameter(String.class, 1, additionalInfo); - String strCustomerName = extractParameter(String.class, 2, additionalInfo); - metaData.putValue("assignedCustomerId", strCustomerId); - metaData.putValue("assignedCustomerName", strCustomerName); - } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) { - String strCustomerId = extractParameter(String.class, 1, additionalInfo); - String strCustomerName = extractParameter(String.class, 2, additionalInfo); - metaData.putValue("unassignedCustomerId", strCustomerId); - metaData.putValue("unassignedCustomerName", strCustomerName); - } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) { - String strTenantId = extractParameter(String.class, 0, additionalInfo); - String strTenantName = extractParameter(String.class, 1, additionalInfo); - metaData.putValue("assignedFromTenantId", strTenantId); - metaData.putValue("assignedFromTenantName", strTenantName); - } else if (actionType == ActionType.ASSIGNED_TO_TENANT) { - String strTenantId = extractParameter(String.class, 0, additionalInfo); - String strTenantName = extractParameter(String.class, 1, additionalInfo); - metaData.putValue("assignedToTenantId", strTenantId); - metaData.putValue("assignedToTenantName", strTenantName); - } else if (actionType == ActionType.ASSIGNED_TO_EDGE) { - String strEdgeId = extractParameter(String.class, 1, additionalInfo); - String strEdgeName = extractParameter(String.class, 2, additionalInfo); - metaData.putValue("assignedEdgeId", strEdgeId); - metaData.putValue("assignedEdgeName", strEdgeName); - } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) { - String strEdgeId = extractParameter(String.class, 1, additionalInfo); - String strEdgeName = extractParameter(String.class, 2, additionalInfo); - metaData.putValue("unassignedEdgeId", strEdgeId); - metaData.putValue("unassignedEdgeName", strEdgeName); - } - ObjectNode entityNode; - if (entity != null) { - entityNode = json.valueToTree(entity); - if (entityId.getEntityType() == EntityType.DASHBOARD) { - entityNode.put("configuration", ""); - } - } else { - entityNode = json.createObjectNode(); - if (actionType == ActionType.ATTRIBUTES_UPDATED) { - String scope = extractParameter(String.class, 0, additionalInfo); - @SuppressWarnings("unchecked") - List attributes = extractParameter(List.class, 1, additionalInfo); - metaData.putValue(DataConstants.SCOPE, scope); - if (attributes != null) { - for (AttributeKvEntry attr : attributes) { - addKvEntry(entityNode, attr); - } - } - } else if (actionType == ActionType.ATTRIBUTES_DELETED) { - String scope = extractParameter(String.class, 0, additionalInfo); - @SuppressWarnings("unchecked") - List keys = extractParameter(List.class, 1, additionalInfo); - metaData.putValue(DataConstants.SCOPE, scope); - ArrayNode attrsArrayNode = entityNode.putArray("attributes"); - if (keys != null) { - keys.forEach(attrsArrayNode::add); - } - } else if (actionType == ActionType.TIMESERIES_UPDATED) { - @SuppressWarnings("unchecked") - List timeseries = extractParameter(List.class, 0, additionalInfo); - addTimeseries(entityNode, timeseries); - } else if (actionType == ActionType.TIMESERIES_DELETED) { - @SuppressWarnings("unchecked") - List keys = extractParameter(List.class, 0, additionalInfo); - if (keys != null) { - ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries"); - keys.forEach(timeseriesArrayNode::add); - } - entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo)); - entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo)); - } - } - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); - TenantId tenantId = user.getTenantId(); - if (tenantId.isNullUid()) { - if (entity instanceof HasTenantId) { - tenantId = ((HasTenantId) entity).getTenantId(); - } - } - tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); - } catch (Exception e) { - log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); - } - } - } - - private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception { - if (kvEntry.getDataType() == DataType.BOOLEAN) { - kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); - } else if (kvEntry.getDataType() == DataType.DOUBLE) { - kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); - } else if (kvEntry.getDataType() == DataType.LONG) { - kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); - } else if (kvEntry.getDataType() == DataType.JSON) { - if (kvEntry.getJsonValue().isPresent()) { - entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get())); - } - } else { - entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString()); - } - } - - private T extractParameter(Class clazz, int index, Object... additionalInfo) { - T result = null; - if (additionalInfo != null && additionalInfo.length > index) { - Object paramObject = additionalInfo[index]; - if (clazz.isInstance(paramObject)) { - result = clazz.cast(paramObject); - } - } - return result; - } - protected String entityToStr(E entity) { try { return json.writeValueAsString(json.valueToTree(entity)); @@ -1093,23 +908,6 @@ public abstract class BaseController { return result; } - private void addTimeseries(ObjectNode entityNode, List timeseries) throws Exception { - if (timeseries != null && !timeseries.isEmpty()) { - ArrayNode result = entityNode.putArray("timeseries"); - Map> groupedTelemetry = timeseries.stream() - .collect(Collectors.groupingBy(TsKvEntry::getTs)); - for (Map.Entry> entry : groupedTelemetry.entrySet()) { - ObjectNode element = json.createObjectNode(); - element.put("ts", entry.getKey()); - ObjectNode values = element.putObject("values"); - for (TsKvEntry tsKvEntry : entry.getValue()) { - addKvEntry(values, tsKvEntry); - } - result.add(element); - } - } - } - protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException { String dashboardId = additionalInfo.has(requiredFields) ? additionalInfo.get(requiredFields).asText() : null; if (dashboardId != null && !dashboardId.equals("null")) { 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 378083b873..b13393515c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -53,6 +53,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; 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.page.TimePageLink; @@ -75,6 +76,7 @@ import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; import static org.thingsboard.server.controller.EdgeController.EDGE_ID; @@ -153,7 +155,7 @@ public class DeviceController extends BaseController { deviceStateService.onDeviceUpdated(savedDevice); } - firmwareStateService.update(savedDevice, oldDevice); + otaPackageStateService.update(savedDevice, oldDevice); return savedDevice; } catch (Exception e) { @@ -778,4 +780,19 @@ public class DeviceController extends BaseController { throw handleException(e); } } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET) + @ResponseBody + public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, + @RequestParam String deviceProfileId) throws ThingsboardException { + checkParameter("OtaPackageType", otaPackageType); + checkParameter("DeviceProfileId", deviceProfileId); + try { + return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( + getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType)); + } catch (Exception e) { + throw handleException(e); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index 849bcb51b9..ceee45147b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -168,7 +168,7 @@ public class DeviceProfileController extends BaseController { null, created ? ActionType.ADDED : ActionType.UPDATED, null); - firmwareStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); + otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(), deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java deleted file mode 100644 index 6728120163..0000000000 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ /dev/null @@ -1,222 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.controller; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.springframework.core.io.ByteArrayResource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.multipart.MultipartFile; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.FirmwareInfo; -import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.permission.Operation; -import org.thingsboard.server.service.security.permission.Resource; - -import java.nio.ByteBuffer; - -@Slf4j -@RestController -@TbCoreComponent -@RequestMapping("/api") -public class FirmwareController extends BaseController { - - public static final String FIRMWARE_ID = "firmwareId"; - public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm"; - - @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") - @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET) - @ResponseBody - public ResponseEntity downloadFirmware(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { - checkParameter(FIRMWARE_ID, strFirmwareId); - try { - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); - Firmware firmware = checkFirmwareId(firmwareId, Operation.READ); - - ByteArrayResource resource = new ByteArrayResource(firmware.getData().array()); - return ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmware.getFileName()) - .header("x-filename", firmware.getFileName()) - .contentLength(resource.contentLength()) - .contentType(parseMediaType(firmware.getContentType())) - .body(resource); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET) - @ResponseBody - public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { - checkParameter(FIRMWARE_ID, strFirmwareId); - try { - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); - return checkNotNull(firmwareService.findFirmwareInfoById(getTenantId(), firmwareId)); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.GET) - @ResponseBody - public Firmware getFirmwareById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { - checkParameter(FIRMWARE_ID, strFirmwareId); - try { - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); - return checkFirmwareId(firmwareId, Operation.READ); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/firmware", method = RequestMethod.POST) - @ResponseBody - public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException { - boolean created = firmwareInfo.getId() == null; - try { - firmwareInfo.setTenantId(getTenantId()); - checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE); - FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo); - logEntityAction(savedFirmwareInfo.getId(), savedFirmwareInfo, - null, created ? ActionType.ADDED : ActionType.UPDATED, null); - return savedFirmwareInfo; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.FIRMWARE), firmwareInfo, - null, created ? ActionType.ADDED : ActionType.UPDATED, e); - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST) - @ResponseBody - public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, - @RequestParam(required = false) String checksum, - @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, - @RequestBody MultipartFile file) throws ThingsboardException { - checkParameter(FIRMWARE_ID, strFirmwareId); - checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); - try { - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); - FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ); - - Firmware firmware = new Firmware(firmwareId); - firmware.setCreatedTime(info.getCreatedTime()); - firmware.setTenantId(getTenantId()); - firmware.setDeviceProfileId(info.getDeviceProfileId()); - firmware.setType(info.getType()); - firmware.setTitle(info.getTitle()); - firmware.setVersion(info.getVersion()); - firmware.setAdditionalInfo(info.getAdditionalInfo()); - - ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); - - byte[] bytes = file.getBytes(); - if (StringUtils.isEmpty(checksum)) { - checksum = firmwareService.generateChecksum(checksumAlgorithm, ByteBuffer.wrap(bytes)); - } - - firmware.setChecksumAlgorithm(checksumAlgorithm); - firmware.setChecksum(checksum); - firmware.setFileName(file.getOriginalFilename()); - firmware.setContentType(file.getContentType()); - firmware.setData(ByteBuffer.wrap(bytes)); - firmware.setDataSize((long) bytes.length); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); - logEntityAction(savedFirmware.getId(), savedFirmware, null, ActionType.UPDATED, null); - return savedFirmware; - } catch (Exception e) { - logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.UPDATED, e, strFirmwareId); - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/firmwares", method = RequestMethod.GET) - @ResponseBody - public PageData getFirmwares(@RequestParam int pageSize, - @RequestParam int page, - @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String sortProperty, - @RequestParam(required = false) String sortOrder) throws ThingsboardException { - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink)); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/firmwares/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET) - @ResponseBody - public PageData getFirmwares(@PathVariable("deviceProfileId") String strDeviceProfileId, - @PathVariable("type") String strType, - @PathVariable("hasData") boolean hasData, - @RequestParam int pageSize, - @RequestParam int page, - @RequestParam(required = false) String textSearch, - @RequestParam(required = false) String sortProperty, - @RequestParam(required = false) String sortOrder) throws ThingsboardException { - checkParameter("deviceProfileId", strDeviceProfileId); - checkParameter("type", strType); - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(), - new DeviceProfileId(toUUID(strDeviceProfileId)), FirmwareType.valueOf(strType), hasData, pageLink)); - } catch (Exception e) { - throw handleException(e); - } - } - - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE) - @ResponseBody - public void deleteFirmware(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException { - checkParameter(FIRMWARE_ID, strFirmwareId); - try { - FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); - FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.DELETE); - firmwareService.deleteFirmware(getTenantId(), firmwareId); - logEntityAction(firmwareId, info, null, ActionType.DELETED, null, strFirmwareId); - } catch (Exception e) { - logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.DELETED, e, strFirmwareId); - throw handleException(e); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java index 6591a1577a..349c6de672 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java +++ b/application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java @@ -22,12 +22,13 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.oauth2.OAuth2Info; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -49,7 +50,8 @@ public class OAuth2Controller extends BaseController { @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @ResponseBody - public List getOAuth2Clients(HttpServletRequest request) throws ThingsboardException { + public List getOAuth2Clients(HttpServletRequest request, + @RequestParam(required = false) String pkgName) throws ThingsboardException { try { if (log.isDebugEnabled()) { log.debug("Executing getOAuth2Clients: [{}][{}][{}]", request.getScheme(), request.getServerName(), request.getServerPort()); @@ -59,7 +61,7 @@ public class OAuth2Controller extends BaseController { log.debug("Header: {} {}", header, request.getHeader(header)); } } - return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request)); + return oAuth2Service.getOAuth2Clients(MiscUtils.getScheme(request), MiscUtils.getDomainNameAndPort(request), pkgName); } catch (Exception e) { throw handleException(e); } @@ -68,10 +70,10 @@ public class OAuth2Controller extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.GET, produces = "application/json") @ResponseBody - public OAuth2ClientsParams getCurrentOAuth2Params() throws ThingsboardException { + public OAuth2Info getCurrentOAuth2Info() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.READ); - return oAuth2Service.findOAuth2Params(); + return oAuth2Service.findOAuth2Info(); } catch (Exception e) { throw handleException(e); } @@ -80,11 +82,11 @@ public class OAuth2Controller extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/oauth2/config", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public OAuth2ClientsParams saveOAuth2Params(@RequestBody OAuth2ClientsParams oauth2Params) throws ThingsboardException { + public OAuth2Info saveOAuth2Info(@RequestBody OAuth2Info oauth2Info) throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.OAUTH2_CONFIGURATION_INFO, Operation.WRITE); - oAuth2Service.saveOAuth2Params(oauth2Params); - return oAuth2Service.findOAuth2Params(); + oAuth2Service.saveOAuth2Info(oauth2Info); + return oAuth2Service.findOAuth2Info(); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java new file mode 100644 index 0000000000..13d39b0b2a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java @@ -0,0 +1,225 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; + +import java.nio.ByteBuffer; + +@Slf4j +@RestController +@TbCoreComponent +@RequestMapping("/api") +public class OtaPackageController extends BaseController { + + public static final String OTA_PACKAGE_ID = "otaPackageId"; + public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm"; + + @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") + @RequestMapping(value = "/otaPackage/{otaPackageId}/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadOtaPackage(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { + checkParameter(OTA_PACKAGE_ID, strOtaPackageId); + try { + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ); + + if (otaPackage.hasUrl()) { + return ResponseEntity.badRequest().build(); + } + + ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName()) + .header("x-filename", otaPackage.getFileName()) + .contentLength(resource.contentLength()) + .contentType(parseMediaType(otaPackage.getContentType())) + .body(resource); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/otaPackage/info/{otaPackageId}", method = RequestMethod.GET) + @ResponseBody + public OtaPackageInfo getOtaPackageInfoById(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { + checkParameter(OTA_PACKAGE_ID, strOtaPackageId); + try { + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + return checkNotNull(otaPackageService.findOtaPackageInfoById(getTenantId(), otaPackageId)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.GET) + @ResponseBody + public OtaPackage getOtaPackageById(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId) throws ThingsboardException { + checkParameter(OTA_PACKAGE_ID, strOtaPackageId); + try { + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + return checkOtaPackageId(otaPackageId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/otaPackage", method = RequestMethod.POST) + @ResponseBody + public OtaPackageInfo saveOtaPackageInfo(@RequestBody OtaPackageInfo otaPackageInfo) throws ThingsboardException { + boolean created = otaPackageInfo.getId() == null; + try { + otaPackageInfo.setTenantId(getTenantId()); + checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE); + OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(otaPackageInfo); + logEntityAction(savedOtaPackageInfo.getId(), savedOtaPackageInfo, + null, created ? ActionType.ADDED : ActionType.UPDATED, null); + return savedOtaPackageInfo; + } catch (Exception e) { + logEntityAction(emptyId(EntityType.OTA_PACKAGE), otaPackageInfo, + null, created ? ActionType.ADDED : ActionType.UPDATED, e); + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.POST) + @ResponseBody + public OtaPackage saveOtaPackageData(@PathVariable(OTA_PACKAGE_ID) String strOtaPackageId, + @RequestParam(required = false) String checksum, + @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, + @RequestBody MultipartFile file) throws ThingsboardException { + checkParameter(OTA_PACKAGE_ID, strOtaPackageId); + checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); + try { + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + OtaPackageInfo info = checkOtaPackageInfoId(otaPackageId, Operation.READ); + + OtaPackage otaPackage = new OtaPackage(otaPackageId); + otaPackage.setCreatedTime(info.getCreatedTime()); + otaPackage.setTenantId(getTenantId()); + otaPackage.setDeviceProfileId(info.getDeviceProfileId()); + otaPackage.setType(info.getType()); + otaPackage.setTitle(info.getTitle()); + otaPackage.setVersion(info.getVersion()); + otaPackage.setAdditionalInfo(info.getAdditionalInfo()); + + ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); + + byte[] bytes = file.getBytes(); + if (StringUtils.isEmpty(checksum)) { + checksum = otaPackageService.generateChecksum(checksumAlgorithm, ByteBuffer.wrap(bytes)); + } + + otaPackage.setChecksumAlgorithm(checksumAlgorithm); + otaPackage.setChecksum(checksum); + otaPackage.setFileName(file.getOriginalFilename()); + otaPackage.setContentType(file.getContentType()); + otaPackage.setData(ByteBuffer.wrap(bytes)); + otaPackage.setDataSize((long) bytes.length); + OtaPackage savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); + logEntityAction(savedOtaPackage.getId(), savedOtaPackage, null, ActionType.UPDATED, null); + return savedOtaPackage; + } catch (Exception e) { + logEntityAction(emptyId(EntityType.OTA_PACKAGE), null, null, ActionType.UPDATED, e, strOtaPackageId); + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/otaPackages", method = RequestMethod.GET) + @ResponseBody + public PageData getOtaPackages(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantId(getTenantId(), pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET) + @ResponseBody + public PageData getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId, + @PathVariable("type") String strType, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + checkParameter("deviceProfileId", strDeviceProfileId); + checkParameter("type", strType); + try { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(), + new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/otaPackage/{otaPackageId}", method = RequestMethod.DELETE) + @ResponseBody + public void deleteOtaPackage(@PathVariable("otaPackageId") String strOtaPackageId) throws ThingsboardException { + checkParameter(OTA_PACKAGE_ID, strOtaPackageId); + try { + OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); + OtaPackageInfo info = checkOtaPackageInfoId(otaPackageId, Operation.DELETE); + otaPackageService.deleteOtaPackage(getTenantId(), otaPackageId); + logEntityAction(otaPackageId, info, null, ActionType.DELETED, null, strOtaPackageId); + } catch (Exception e) { + logEntityAction(emptyId(EntityType.OTA_PACKAGE), null, null, ActionType.DELETED, e, strOtaPackageId); + throw handleException(e); + } + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java new file mode 100644 index 0000000000..f1320d1a7a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java @@ -0,0 +1,256 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.action; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.CustomerId; +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.kv.DataType; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +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.queue.TbClusterService; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@TbCoreComponent +@Service +@RequiredArgsConstructor +@Slf4j +public class RuleEngineEntityActionService { + private final TbClusterService tbClusterService; + + private static final ObjectMapper json = new ObjectMapper(); + + public void pushEntityActionToRuleEngine(EntityId entityId, HasName entity, TenantId tenantId, CustomerId customerId, + ActionType actionType, User user, Object... additionalInfo) { + String msgType = null; + switch (actionType) { + case ADDED: + msgType = DataConstants.ENTITY_CREATED; + break; + case DELETED: + msgType = DataConstants.ENTITY_DELETED; + break; + case UPDATED: + msgType = DataConstants.ENTITY_UPDATED; + break; + case ASSIGNED_TO_CUSTOMER: + msgType = DataConstants.ENTITY_ASSIGNED; + break; + case UNASSIGNED_FROM_CUSTOMER: + msgType = DataConstants.ENTITY_UNASSIGNED; + break; + case ATTRIBUTES_UPDATED: + msgType = DataConstants.ATTRIBUTES_UPDATED; + break; + case ATTRIBUTES_DELETED: + msgType = DataConstants.ATTRIBUTES_DELETED; + break; + case ALARM_ACK: + msgType = DataConstants.ALARM_ACK; + break; + case ALARM_CLEAR: + msgType = DataConstants.ALARM_CLEAR; + break; + case ALARM_DELETE: + msgType = DataConstants.ALARM_DELETE; + break; + case ASSIGNED_FROM_TENANT: + msgType = DataConstants.ENTITY_ASSIGNED_FROM_TENANT; + break; + case ASSIGNED_TO_TENANT: + msgType = DataConstants.ENTITY_ASSIGNED_TO_TENANT; + break; + case PROVISION_SUCCESS: + msgType = DataConstants.PROVISION_SUCCESS; + break; + case PROVISION_FAILURE: + msgType = DataConstants.PROVISION_FAILURE; + break; + case TIMESERIES_UPDATED: + msgType = DataConstants.TIMESERIES_UPDATED; + break; + case TIMESERIES_DELETED: + msgType = DataConstants.TIMESERIES_DELETED; + break; + case ASSIGNED_TO_EDGE: + msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE; + break; + case UNASSIGNED_FROM_EDGE: + msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE; + break; + } + if (!StringUtils.isEmpty(msgType)) { + try { + TbMsgMetaData metaData = new TbMsgMetaData(); + if (user != null) { + metaData.putValue("userId", user.getId().toString()); + metaData.putValue("userName", user.getName()); + } + if (customerId != null && !customerId.isNullUid()) { + metaData.putValue("customerId", customerId.toString()); + } + if (actionType == ActionType.ASSIGNED_TO_CUSTOMER) { + String strCustomerId = extractParameter(String.class, 1, additionalInfo); + String strCustomerName = extractParameter(String.class, 2, additionalInfo); + metaData.putValue("assignedCustomerId", strCustomerId); + metaData.putValue("assignedCustomerName", strCustomerName); + } else if (actionType == ActionType.UNASSIGNED_FROM_CUSTOMER) { + String strCustomerId = extractParameter(String.class, 1, additionalInfo); + String strCustomerName = extractParameter(String.class, 2, additionalInfo); + metaData.putValue("unassignedCustomerId", strCustomerId); + metaData.putValue("unassignedCustomerName", strCustomerName); + } else if (actionType == ActionType.ASSIGNED_FROM_TENANT) { + String strTenantId = extractParameter(String.class, 0, additionalInfo); + String strTenantName = extractParameter(String.class, 1, additionalInfo); + metaData.putValue("assignedFromTenantId", strTenantId); + metaData.putValue("assignedFromTenantName", strTenantName); + } else if (actionType == ActionType.ASSIGNED_TO_TENANT) { + String strTenantId = extractParameter(String.class, 0, additionalInfo); + String strTenantName = extractParameter(String.class, 1, additionalInfo); + metaData.putValue("assignedToTenantId", strTenantId); + metaData.putValue("assignedToTenantName", strTenantName); + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) { + String strEdgeId = extractParameter(String.class, 1, additionalInfo); + String strEdgeName = extractParameter(String.class, 2, additionalInfo); + metaData.putValue("assignedEdgeId", strEdgeId); + metaData.putValue("assignedEdgeName", strEdgeName); + } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) { + String strEdgeId = extractParameter(String.class, 1, additionalInfo); + String strEdgeName = extractParameter(String.class, 2, additionalInfo); + metaData.putValue("unassignedEdgeId", strEdgeId); + metaData.putValue("unassignedEdgeName", strEdgeName); + } + ObjectNode entityNode; + if (entity != null) { + entityNode = json.valueToTree(entity); + if (entityId.getEntityType() == EntityType.DASHBOARD) { + entityNode.put("configuration", ""); + } + } else { + entityNode = json.createObjectNode(); + if (actionType == ActionType.ATTRIBUTES_UPDATED) { + String scope = extractParameter(String.class, 0, additionalInfo); + @SuppressWarnings("unchecked") + List attributes = extractParameter(List.class, 1, additionalInfo); + metaData.putValue(DataConstants.SCOPE, scope); + if (attributes != null) { + for (AttributeKvEntry attr : attributes) { + addKvEntry(entityNode, attr); + } + } + } else if (actionType == ActionType.ATTRIBUTES_DELETED) { + String scope = extractParameter(String.class, 0, additionalInfo); + @SuppressWarnings("unchecked") + List keys = extractParameter(List.class, 1, additionalInfo); + metaData.putValue(DataConstants.SCOPE, scope); + ArrayNode attrsArrayNode = entityNode.putArray("attributes"); + if (keys != null) { + keys.forEach(attrsArrayNode::add); + } + } else if (actionType == ActionType.TIMESERIES_UPDATED) { + @SuppressWarnings("unchecked") + List timeseries = extractParameter(List.class, 0, additionalInfo); + addTimeseries(entityNode, timeseries); + } else if (actionType == ActionType.TIMESERIES_DELETED) { + @SuppressWarnings("unchecked") + List keys = extractParameter(List.class, 0, additionalInfo); + if (keys != null) { + ArrayNode timeseriesArrayNode = entityNode.putArray("timeseries"); + keys.forEach(timeseriesArrayNode::add); + } + entityNode.put("startTs", extractParameter(Long.class, 1, additionalInfo)); + entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo)); + } + } + TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); + if (tenantId.isNullUid()) { + if (entity instanceof HasTenantId) { + tenantId = ((HasTenantId) entity).getTenantId(); + } + } + tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); + } catch (Exception e) { + log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); + } + } + } + + + private T extractParameter(Class clazz, int index, Object... additionalInfo) { + T result = null; + if (additionalInfo != null && additionalInfo.length > index) { + Object paramObject = additionalInfo[index]; + if (clazz.isInstance(paramObject)) { + result = clazz.cast(paramObject); + } + } + return result; + } + + private void addTimeseries(ObjectNode entityNode, List timeseries) throws Exception { + if (timeseries != null && !timeseries.isEmpty()) { + ArrayNode result = entityNode.putArray("timeseries"); + Map> groupedTelemetry = timeseries.stream() + .collect(Collectors.groupingBy(TsKvEntry::getTs)); + for (Map.Entry> entry : groupedTelemetry.entrySet()) { + ObjectNode element = json.createObjectNode(); + element.put("ts", entry.getKey()); + ObjectNode values = element.putObject("values"); + for (TsKvEntry tsKvEntry : entry.getValue()) { + addKvEntry(values, tsKvEntry); + } + result.add(element); + } + } + } + + private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception { + if (kvEntry.getDataType() == DataType.BOOLEAN) { + kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + } else if (kvEntry.getDataType() == DataType.DOUBLE) { + kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + } else if (kvEntry.getDataType() == DataType.LONG) { + kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + } else if (kvEntry.getDataType() == DataType.JSON) { + if (kvEntry.getJsonValue().isPresent()) { + entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get())); + } + } else { + entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString()); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index b91c46417c..8c27417f90 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.server.common.data.EntityView; @@ -35,6 +36,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; @@ -45,10 +48,11 @@ import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.oauth2.OAuth2Service; +import org.thingsboard.server.dao.oauth2.OAuth2Utils; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.service.install.InstallScripts; import java.util.ArrayList; @@ -88,6 +92,9 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired private AlarmDao alarmDao; + @Autowired + private OAuth2Service oAuth2Service; + @Override public void updateData(String fromVersion) throws Exception { switch (fromVersion) { @@ -107,6 +114,7 @@ public class DefaultDataUpdateService implements DataUpdateService { log.info("Updating data from version 3.2.2 to 3.3.0 ..."); tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); tenantsAlarmsCustomerUpdater.updateEntities(null); + updateOAuth2Params(); break; default: throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); @@ -362,4 +370,20 @@ public class DefaultDataUpdateService implements DataUpdateService { } } + private void updateOAuth2Params() { + try { + OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params(); + if (!oauth2ClientsParams.getDomainsParams().isEmpty()) { + log.info("Updating OAuth2 parameters ..."); + OAuth2Info oAuth2Info = OAuth2Utils.clientParamsToOAuth2Info(oauth2ClientsParams); + oAuth2Service.saveOAuth2Info(oAuth2Info); + oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList())); + log.info("Successfully updated OAuth2 parameters!"); + } + } + catch (Exception e) { + log.error("Failed to update OAuth2 parameters", e); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java similarity index 60% rename from application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java rename to application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java index 9720cd2097..2ba4735d55 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.firmware; +package org.thingsboard.server.service.ota; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; @@ -23,12 +23,10 @@ import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.FirmwareInfo; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus; -import org.thingsboard.server.common.data.firmware.FirmwareUtil; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -37,13 +35,16 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; +import org.thingsboard.server.common.data.ota.OtaPackageUtil; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.firmware.FirmwareService; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; @@ -58,44 +59,44 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; -import java.util.function.Function; - -import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM; -import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM_ALGORITHM; -import static org.thingsboard.server.common.data.firmware.FirmwareKey.SIZE; -import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE; -import static org.thingsboard.server.common.data.firmware.FirmwareKey.TITLE; -import static org.thingsboard.server.common.data.firmware.FirmwareKey.TS; -import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION; -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; -import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE; -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey; -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTargetTelemetryKey; -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTelemetryKey; + +import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.CHECKSUM_ALGORITHM; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getTargetTelemetryKey; +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getTelemetryKey; @Slf4j @Service @TbCoreComponent -public class DefaultFirmwareStateService implements FirmwareStateService { +public class DefaultOtaPackageStateService implements OtaPackageStateService { private final TbClusterService tbClusterService; - private final FirmwareService firmwareService; + private final OtaPackageService otaPackageService; private final DeviceService deviceService; private final DeviceProfileService deviceProfileService; private final RuleEngineTelemetryService telemetryService; - private final TbQueueProducer> fwStateMsgProducer; + private final TbQueueProducer> otaPackageStateMsgProducer; - public DefaultFirmwareStateService(TbClusterService tbClusterService, FirmwareService firmwareService, - DeviceService deviceService, - DeviceProfileService deviceProfileService, - RuleEngineTelemetryService telemetryService, - TbCoreQueueFactory coreQueueFactory) { + public DefaultOtaPackageStateService(TbClusterService tbClusterService, OtaPackageService otaPackageService, + DeviceService deviceService, + DeviceProfileService deviceProfileService, + RuleEngineTelemetryService telemetryService, + TbCoreQueueFactory coreQueueFactory) { this.tbClusterService = tbClusterService; - this.firmwareService = firmwareService; + this.otaPackageService = otaPackageService; this.deviceService = deviceService; this.deviceProfileService = deviceProfileService; this.telemetryService = telemetryService; - this.fwStateMsgProducer = coreQueueFactory.createToFirmwareStateServiceMsgProducer(); + this.otaPackageStateMsgProducer = coreQueueFactory.createToOtaPackageStateServiceMsgProducer(); } @Override @@ -105,14 +106,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } private void updateFirmware(Device device, Device oldDevice) { - FirmwareId newFirmwareId = device.getFirmwareId(); + OtaPackageId newFirmwareId = device.getFirmwareId(); if (newFirmwareId == null) { DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); newFirmwareId = newDeviceProfile.getFirmwareId(); } if (oldDevice != null) { if (newFirmwareId != null) { - FirmwareId oldFirmwareId = oldDevice.getFirmwareId(); + OtaPackageId oldFirmwareId = oldDevice.getFirmwareId(); if (oldFirmwareId == null) { DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); oldFirmwareId = oldDeviceProfile.getFirmwareId(); @@ -132,14 +133,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } private void updateSoftware(Device device, Device oldDevice) { - FirmwareId newSoftwareId = device.getSoftwareId(); + OtaPackageId newSoftwareId = device.getSoftwareId(); if (newSoftwareId == null) { DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); newSoftwareId = newDeviceProfile.getSoftwareId(); } if (oldDevice != null) { if (newSoftwareId != null) { - FirmwareId oldSoftwareId = oldDevice.getSoftwareId(); + OtaPackageId oldSoftwareId = oldDevice.getSoftwareId(); if (oldSoftwareId == null) { DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); oldSoftwareId = oldDeviceProfile.getSoftwareId(); @@ -170,33 +171,20 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } } - private void update(TenantId tenantId, DeviceProfile deviceProfile, FirmwareType firmwareType) { - Function> getDevicesFunction; + private void update(TenantId tenantId, DeviceProfile deviceProfile, OtaPackageType otaPackageType) { Consumer updateConsumer; - switch (firmwareType) { - case FIRMWARE: - getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId, deviceProfile.getName(), pl); - break; - case SOFTWARE: - getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptySoftware(tenantId, deviceProfile.getName(), pl); - break; - default: - log.warn("Unsupported firmware type: [{}]", firmwareType); - return; - } - if (deviceProfile.getFirmwareId() != null) { long ts = System.currentTimeMillis(); - updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, firmwareType); + updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, otaPackageType); } else { - updateConsumer = d -> remove(d, firmwareType); + updateConsumer = d -> remove(d, otaPackageType); } PageLink pageLink = new PageLink(100); PageData pageData; do { - pageData = getDevicesFunction.apply(pageLink); + pageData = deviceService.findDevicesByTenantIdAndTypeAndEmptyOtaPackage(tenantId, deviceProfile.getId(), otaPackageType, pageLink); pageData.getData().forEach(updateConsumer); if (pageData.hasNext()) { @@ -206,60 +194,60 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } @Override - public boolean process(ToFirmwareStateServiceMsg msg) { + public boolean process(ToOtaPackageStateServiceMsg msg) { boolean isSuccess = false; - FirmwareId targetFirmwareId = new FirmwareId(new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB())); + OtaPackageId targetOtaPackageId = new OtaPackageId(new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB())); DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); - FirmwareType firmwareType = FirmwareType.valueOf(msg.getType()); + OtaPackageType firmwareType = OtaPackageType.valueOf(msg.getType()); long ts = msg.getTs(); Device device = deviceService.findDeviceById(tenantId, deviceId); if (device == null) { log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId); } else { - FirmwareId currentFirmwareId = FirmwareUtil.getFirmwareId(device, firmwareType); - if (currentFirmwareId == null) { + OtaPackageId currentOtaPackageId = OtaPackageUtil.getOtaPackageId(device, firmwareType); + if (currentOtaPackageId == null) { DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()); - currentFirmwareId = FirmwareUtil.getFirmwareId(deviceProfile, firmwareType); + currentOtaPackageId = OtaPackageUtil.getOtaPackageId(deviceProfile, firmwareType); } - if (targetFirmwareId.equals(currentFirmwareId)) { - update(device, firmwareService.findFirmwareInfoById(device.getTenantId(), targetFirmwareId), ts); + if (targetOtaPackageId.equals(currentOtaPackageId)) { + update(device, otaPackageService.findOtaPackageInfoById(device.getTenantId(), targetOtaPackageId), ts); isSuccess = true; } else { - log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId); + log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetOtaPackageId, currentOtaPackageId); } } return isSuccess; } - private void send(TenantId tenantId, DeviceId deviceId, FirmwareId firmwareId, long ts, FirmwareType firmwareType) { - ToFirmwareStateServiceMsg msg = ToFirmwareStateServiceMsg.newBuilder() + private void send(TenantId tenantId, DeviceId deviceId, OtaPackageId firmwareId, long ts, OtaPackageType firmwareType) { + ToOtaPackageStateServiceMsg msg = ToOtaPackageStateServiceMsg.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) - .setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()) - .setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()) + .setOtaPackageIdMSB(firmwareId.getId().getMostSignificantBits()) + .setOtaPackageIdLSB(firmwareId.getId().getLeastSignificantBits()) .setType(firmwareType.name()) .setTs(ts) .build(); - FirmwareInfo firmware = firmwareService.findFirmwareInfoById(tenantId, firmwareId); + OtaPackageInfo firmware = otaPackageService.findOtaPackageInfoById(tenantId, firmwareId); if (firmware == null) { log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId); return; } - TopicPartitionInfo tpi = new TopicPartitionInfo(fwStateMsgProducer.getDefaultTopic(), null, null, false); - fwStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); + TopicPartitionInfo tpi = new TopicPartitionInfo(otaPackageStateMsgProducer.getDefaultTopic(), null, null, false); + otaPackageStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); List telemetry = new ArrayList<>(); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), TITLE), firmware.getTitle()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTargetTelemetryKey(firmware.getType(), VERSION), firmware.getVersion()))); telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(getTargetTelemetryKey(firmware.getType(), TS), ts))); - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), FirmwareUpdateStatus.QUEUED.name()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.QUEUED.name()))); telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { @Override @@ -275,11 +263,12 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } - private void update(Device device, FirmwareInfo firmware, long ts) { + private void update(Device device, OtaPackageInfo otaPackage, long ts) { TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); + OtaPackageType otaPackageType = otaPackage.getType(); - BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), FirmwareUpdateStatus.INITIATED.name())); + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name())); telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { @Override @@ -294,11 +283,37 @@ public class DefaultFirmwareStateService implements FirmwareStateService { }); List attributes = new ArrayList<>(); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle()))); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion()))); - attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize()))); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name()))); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion()))); + if (otaPackage.hasUrl()) { + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl()))); + List attrToRemove = new ArrayList<>(); + + if (otaPackage.getDataSize() == null) { + attrToRemove.add(getAttributeKey(otaPackageType, SIZE)); + } else { + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize()))); + } + + if (otaPackage.getChecksumAlgorithm() != null) { + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM)); + } else { + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name()))); + } + + if (StringUtils.isEmpty(otaPackage.getChecksum())) { + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM)); + } else { + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum()))); + } + + remove(device, otaPackageType, attrToRemove); + } else { + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum()))); + remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL))); + } telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { @Override @@ -313,20 +328,24 @@ public class DefaultFirmwareStateService implements FirmwareStateService { }); } - private void remove(Device device, FirmwareType firmwareType) { - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, FirmwareUtil.getAttributeKeys(firmwareType), + private void remove(Device device, OtaPackageType otaPackageType) { + remove(device, otaPackageType, OtaPackageUtil.getAttributeKeys(otaPackageType)); + } + + private void remove(Device device, OtaPackageType otaPackageType, List attributesKeys) { + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys, new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { - log.trace("[{}] Success remove target firmware attributes!", device.getId()); + log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType); Set keysToNotify = new HashSet<>(); - FirmwareUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); + attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); } @Override public void onFailure(Throwable t) { - log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t); + log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t); } }); } diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java similarity index 79% rename from application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java rename to application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java index 8562f096c9..9392d1c888 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/OtaPackageStateService.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.firmware; +package org.thingsboard.server.service.ota; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; -public interface FirmwareStateService { +public interface OtaPackageStateService { void update(Device device, Device oldDevice); void update(DeviceProfile deviceProfile, boolean isFirmwareChanged, boolean isSoftwareChanged); - boolean process(ToFirmwareStateServiceMsg msg); + boolean process(ToOtaPackageStateServiceMsg msg); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index b8c8698d52..ceb364f921 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -50,7 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.queue.TbQueueConsumer; @@ -60,7 +60,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; -import org.thingsboard.server.service.firmware.FirmwareStateService; +import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.IdMsgPair; @@ -75,7 +75,6 @@ import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWra import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -101,9 +100,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> mainConsumer; @@ -113,10 +112,10 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> usageStatsConsumer; - private final TbQueueConsumer> firmwareStatesConsumer; + private final TbQueueConsumer> firmwareStatesConsumer; protected volatile ExecutorService usageStatsExecutor; @@ -135,11 +134,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { while (!stopped) { try { - List> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration()); + List> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration()); if (msgs.isEmpty()) { continue; } long timeToSleep = maxProcessingTimeoutPerRecord; - for (TbProtoQueueMsg msg : msgs) { + for (TbProtoQueueMsg msg : msgs) { try { long startTime = System.currentTimeMillis(); - boolean isSuccessUpdate = handleFirmwareUpdates(msg); + boolean isSuccessUpdate = handleOtaPackageUpdates(msg); long endTime = System.currentTimeMillis(); long spentTime = endTime - startTime; timeToSleep = timeToSleep - spentTime; @@ -402,7 +401,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService msg) { + private boolean handleOtaPackageUpdates(TbProtoQueueMsg msg) { return firmwareStateService.process(msg.getValue()); } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index 2cde0e2113..cd8aa88863 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -72,15 +72,15 @@ public class DefaultTbResourceService implements TbResourceService { if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { try { List objectModels = - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); if (!objectModels.isEmpty()) { ObjectModel objectModel = objectModels.get(0); - String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion(); + String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.version; String name = objectModel.name; resource.setResourceKey(resourceKey); if (resource.getId() == null) { - resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion()); + resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.version); } resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); } else { @@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService { resourceService.deleteResourcesByTenantId(tenantId); } + @Override + public long sumDataSizeByTenantId(TenantId tenantId) { + return resourceService.sumDataSizeByTenantId(tenantId); + } + private Comparator getComparator(String sortProperty, String sortOrder) { Comparator comparator; if ("name".equals(sortProperty)) { @@ -171,7 +176,7 @@ public class DefaultTbResourceService implements TbResourceService { try { DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); List objectModels = - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); if (objectModels.size() == 0) { return null; } else { diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 7ad1848138..d3d079f548 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -55,4 +55,5 @@ public interface TbResourceService { void deleteResourcesByTenantId(TenantId tenantId); + long sumDataSizeByTenantId(TenantId tenantId); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 914b9ce7fa..540a47e8b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; @@ -46,7 +46,7 @@ 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.EntityViewId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TbResourceId; @@ -63,7 +63,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -135,7 +135,7 @@ public class AccessValidator { protected ResourceService resourceService; @Autowired - protected FirmwareService firmwareService; + protected OtaPackageService otaPackageService; private ExecutorService executor; @@ -232,8 +232,8 @@ public class AccessValidator { case TB_RESOURCE: validateResource(currentUser, operation, entityId, callback); return; - case FIRMWARE: - validateFirmware(currentUser, operation, entityId, callback); + case OTA_PACKAGE: + validateOtaPackage(currentUser, operation, entityId, callback); return; default: //TODO: add support of other entities @@ -300,20 +300,20 @@ public class AccessValidator { } } - private void validateFirmware(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { + private void validateOtaPackage(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { - FirmwareInfo firmware = firmwareService.findFirmwareInfoById(currentUser.getTenantId(), new FirmwareId(entityId.getId())); - if (firmware == null) { - callback.onSuccess(ValidationResult.entityNotFound("Firmware with requested id wasn't found!")); + OtaPackageInfo otaPackage = otaPackageService.findOtaPackageInfoById(currentUser.getTenantId(), new OtaPackageId(entityId.getId())); + if (otaPackage == null) { + callback.onSuccess(ValidationResult.entityNotFound("OtaPackage with requested id wasn't found!")); } else { try { - accessControlService.checkPermission(currentUser, Resource.FIRMWARE, operation, entityId, firmware); + accessControlService.checkPermission(currentUser, Resource.OTA_PACKAGE, operation, entityId, otaPackage); } catch (ThingsboardException e) { callback.onSuccess(ValidationResult.accessDenied(e.getMessage())); } - callback.onSuccess(ValidationResult.ok(firmware)); + callback.onSuccess(ValidationResult.ok(otaPackage)); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 4f38cf7a23..3ff3534806 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -33,8 +33,8 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; @@ -93,9 +93,9 @@ public abstract class AbstractOAuth2ClientMapper { private final Lock userCreationLock = new ReentrantLock(); - protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) { + protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2Registration registration) { - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); + OAuth2MapperConfig config = registration.getMapperConfig(); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, oauth2User.getEmail()); @@ -139,9 +139,9 @@ public abstract class AbstractOAuth2ClientMapper { } } - if (clientRegistration.getAdditionalInfo() != null && - clientRegistration.getAdditionalInfo().has("providerName")) { - additionalInfo.put("authProviderName", clientRegistration.getAdditionalInfo().get("providerName").asText()); + if (registration.getAdditionalInfo() != null && + registration.getAdditionalInfo().has("providerName")) { + additionalInfo.put("authProviderName", registration.getAdditionalInfo().get("providerName").asText()); } user.setAdditionalInfo(additionalInfo); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index 940bf8ad0a..f5172b64e7 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -18,8 +18,8 @@ package org.thingsboard.server.service.security.auth.oauth2; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -30,12 +30,12 @@ import java.util.Map; public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + OAuth2MapperConfig config = registration.getMapperConfig(); Map attributes = token.getPrincipal().getAttributes(); String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index cb08bc9f96..65ebb384de 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -23,9 +23,9 @@ import org.springframework.security.oauth2.client.authentication.OAuth2Authentic import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.client.RestTemplate; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -39,10 +39,10 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + OAuth2MapperConfig config = registration.getMapperConfig(); OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); - return getOrCreateSecurityUserFromOAuth2User(oauth2User, clientRegistration); + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); } private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2CustomMapperConfig custom) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java index 8e41c4a747..d6260fe482 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -23,8 +23,8 @@ import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; @@ -46,13 +46,13 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private OAuth2Configuration oAuth2Configuration; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration) { - OAuth2MapperConfig config = clientRegistration.getMapperConfig(); + public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + OAuth2MapperConfig config = registration.getMapperConfig(); Map githubMapperConfig = oAuth2Configuration.getGithubMapper(); String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); Map attributes = token.getPrincipal().getAttributes(); OAuth2User oAuth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); - return getOrCreateSecurityUserFromOAuth2User(oAuth2User, clientRegistration); + return getOrCreateSecurityUserFromOAuth2User(oAuth2User, registration); } private synchronized String getEmail(String emailUrl, String oauth2Token) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 965b34d8b0..280418d066 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -16,9 +16,10 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; import org.thingsboard.server.service.security.model.SecurityUser; public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2ClientRegistrationInfo clientRegistration); + SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index b6345f1618..0e413b4f22 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -18,8 +18,10 @@ package org.thingsboard.server.service.security.auth.oauth2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -51,9 +53,19 @@ public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationF public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + String baseUrl; + String errorPrefix; + OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request); + String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME); + if (!StringUtils.isEmpty(callbackUrlScheme)) { + baseUrl = callbackUrlScheme + ":"; + errorPrefix = "/?error="; + } else { + baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + errorPrefix = "/login?loginError="; + } httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response); - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + + getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 72a6c65f7c..227d733ebd 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -20,12 +20,14 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.security.model.JwtToken; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; @@ -72,17 +74,24 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + OAuth2AuthorizationRequest authorizationRequest = httpCookieOAuth2AuthorizationRequestRepository.loadAuthorizationRequest(request); + String callbackUrlScheme = authorizationRequest.getAttribute(TbOAuth2ParameterNames.CALLBACK_URL_SCHEME); + String baseUrl; + if (!StringUtils.isEmpty(callbackUrlScheme)) { + baseUrl = callbackUrlScheme + ":"; + } else { + baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + } try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; - OAuth2ClientRegistrationInfo clientRegistration = oAuth2Service.findClientRegistrationInfo(UUID.fromString(token.getAuthorizedClientRegistrationId())); + OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(token.getAuthorizedClientRegistrationId())); OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient( token.getAuthorizedClientRegistrationId(), token.getPrincipal().getName()); - OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(clientRegistration.getMapperConfig().getType()); + OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType()); SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), - clientRegistration); + registration); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); @@ -91,7 +100,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); } catch (Exception e) { clearAuthenticationAttributes(request, response); - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + + String errorPrefix; + if (!StringUtils.isEmpty(callbackUrlScheme)) { + errorPrefix = "/?error="; + } else { + errorPrefix = "/login?loginError="; + } + getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix + URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString())); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/TbOAuth2ParameterNames.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/TbOAuth2ParameterNames.java new file mode 100644 index 0000000000..aa5b32f055 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/TbOAuth2ParameterNames.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.auth.oauth2; + +public interface TbOAuth2ParameterNames { + + String CALLBACK_URL_SCHEME = "callback_url_scheme"; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 75119c402d..43c420a94a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -38,7 +38,7 @@ public enum Resource { DEVICE_PROFILE(EntityType.DEVICE_PROFILE), API_USAGE_STATE(EntityType.API_USAGE_STATE), TB_RESOURCE(EntityType.TB_RESOURCE), - FIRMWARE(EntityType.FIRMWARE), + OTA_PACKAGE(EntityType.OTA_PACKAGE), EDGE(EntityType.EDGE); private final EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index af08e38087..8b4d44e938 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -42,7 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); put(Resource.TB_RESOURCE, tbResourcePermissionChecker); - put(Resource.FIRMWARE, tenantEntityPermissionChecker); + put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker); put(Resource.EDGE, tenantEntityPermissionChecker); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index bd2df29617..980c3d2c10 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -26,27 +26,27 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.cache.firmware.FirmwareDataCache; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.firmware.FirmwareUtil; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUtil; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -64,7 +64,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; -import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; @@ -124,8 +124,8 @@ public class DefaultTransportApiService implements TransportApiService { private final DataDecodingEncodingService dataDecodingEncodingService; private final DeviceProvisionService deviceProvisionService; private final TbResourceService resourceService; - private final FirmwareService firmwareService; - private final FirmwareDataCache firmwareDataCache; + private final OtaPackageService otaPackageService; + private final OtaPackageDataCache otaPackageDataCache; private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); @@ -134,7 +134,7 @@ public class DefaultTransportApiService implements TransportApiService { RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, - DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareDataCache firmwareDataCache) { + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, OtaPackageService otaPackageService, OtaPackageDataCache otaPackageDataCache) { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.apiUsageStateService = apiUsageStateService; @@ -147,8 +147,8 @@ public class DefaultTransportApiService implements TransportApiService { this.dataDecodingEncodingService = dataDecodingEncodingService; this.deviceProvisionService = deviceProvisionService; this.resourceService = resourceService; - this.firmwareService = firmwareService; - this.firmwareDataCache = firmwareDataCache; + this.otaPackageService = otaPackageService; + this.otaPackageDataCache = otaPackageDataCache; } @Override @@ -184,8 +184,8 @@ public class DefaultTransportApiService implements TransportApiService { result = handle(transportApiRequestMsg.getDeviceRequestMsg()); } else if (transportApiRequestMsg.hasDeviceCredentialsRequestMsg()) { result = handle(transportApiRequestMsg.getDeviceCredentialsRequestMsg()); - } else if (transportApiRequestMsg.hasFirmwareRequestMsg()) { - result = handle(transportApiRequestMsg.getFirmwareRequestMsg()); + } else if (transportApiRequestMsg.hasOtaPackageRequestMsg()) { + result = handle(transportApiRequestMsg.getOtaPackageRequestMsg()); } return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture), @@ -511,50 +511,53 @@ public class DefaultTransportApiService implements TransportApiService { } } - private ListenableFuture handle(TransportProtos.GetFirmwareRequestMsg requestMsg) { + private ListenableFuture handle(TransportProtos.GetOtaPackageRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB())); - FirmwareType firmwareType = FirmwareType.valueOf(requestMsg.getType()); + OtaPackageType otaPackageType = OtaPackageType.valueOf(requestMsg.getType()); Device device = deviceService.findDeviceById(tenantId, deviceId); if (device == null) { return getEmptyTransportApiResponseFuture(); } - FirmwareId firmwareId = FirmwareUtil.getFirmwareId(device, firmwareType); - if (firmwareId == null) { + OtaPackageId otaPackageId = OtaPackageUtil.getOtaPackageId(device, otaPackageType); + if (otaPackageId == null) { DeviceProfile deviceProfile = deviceProfileCache.find(device.getDeviceProfileId()); - firmwareId = FirmwareUtil.getFirmwareId(deviceProfile, firmwareType); + otaPackageId = OtaPackageUtil.getOtaPackageId(deviceProfile, otaPackageType); } - TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder(); + TransportProtos.GetOtaPackageResponseMsg.Builder builder = TransportProtos.GetOtaPackageResponseMsg.newBuilder(); - if (firmwareId == null) { + if (otaPackageId == null) { builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); } else { - FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(tenantId, firmwareId); + OtaPackageInfo otaPackageInfo = otaPackageService.findOtaPackageInfoById(tenantId, otaPackageId); - if (firmwareInfo == null) { + if (otaPackageInfo == null) { builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); + } else if (otaPackageInfo.hasUrl()) { + builder.setResponseStatus(TransportProtos.ResponseStatus.FAILURE); + log.trace("[{}] Can`t send OtaPackage with URL data!", otaPackageInfo.getId()); } else { builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS); - builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()); - builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()); - builder.setType(firmwareInfo.getType().name()); - builder.setTitle(firmwareInfo.getTitle()); - builder.setVersion(firmwareInfo.getVersion()); - builder.setFileName(firmwareInfo.getFileName()); - builder.setContentType(firmwareInfo.getContentType()); - if (!firmwareDataCache.has(firmwareId.toString())) { - Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId); - firmwareDataCache.put(firmwareId.toString(), firmware.getData().array()); + builder.setOtaPackageIdMSB(otaPackageId.getId().getMostSignificantBits()); + builder.setOtaPackageIdLSB(otaPackageId.getId().getLeastSignificantBits()); + builder.setType(otaPackageInfo.getType().name()); + builder.setTitle(otaPackageInfo.getTitle()); + builder.setVersion(otaPackageInfo.getVersion()); + builder.setFileName(otaPackageInfo.getFileName()); + builder.setContentType(otaPackageInfo.getContentType()); + if (!otaPackageDataCache.has(otaPackageId.toString())) { + OtaPackage otaPackage = otaPackageService.findOtaPackageById(tenantId, otaPackageId); + otaPackageDataCache.put(otaPackageId.toString(), otaPackage.getData().array()); } } } return Futures.immediateFuture( TransportApiResponseMsg.newBuilder() - .setFirmwareResponseMsg(builder.build()) + .setOtaPackageResponseMsg(builder.build()) .build()); } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java index 95731d2988..05799fc643 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java @@ -17,9 +17,9 @@ package org.thingsboard.server.service.ttl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.thingsboard.server.dao.util.PsqlDao; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; @@ -62,4 +62,8 @@ public abstract class AbstractCleanUpService { protected abstract void doCleanUp(Connection connection) throws SQLException; + protected Connection getConnection() throws SQLException { + return DriverManager.getConnection(dbUrl, dbUserName, dbPassword); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/alarms/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/alarms/AlarmsCleanUpService.java new file mode 100644 index 0000000000..3b76a6cbca --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ttl/alarms/AlarmsCleanUpService.java @@ -0,0 +1,110 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.ttl.alarms; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.AlarmId; +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.SortOrder; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.dao.alarm.AlarmDao; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.action.RuleEngineEntityActionService; +import org.thingsboard.server.service.ttl.AbstractCleanUpService; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Date; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +@TbCoreComponent +@Service +@Slf4j +@RequiredArgsConstructor +public class AlarmsCleanUpService { + @Value("${sql.ttl.alarms.removal_batch_size}") + private Integer removalBatchSize; + + private final TenantDao tenantDao; + private final AlarmDao alarmDao; + private final AlarmService alarmService; + private final RelationService relationService; + private final RuleEngineEntityActionService ruleEngineEntityActionService; + private final PartitionService partitionService; + private final TbTenantProfileCache tenantProfileCache; + + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.alarms.checking_interval})}", fixedDelayString = "${sql.ttl.alarms.checking_interval}") + public void cleanUp() { + PageLink tenantsBatchRequest = new PageLink(10_000, 0); + PageLink removalBatchRequest = new PageLink(removalBatchSize, 0 ); + PageData tenantsIds; + do { + tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest); + for (TenantId tenantId : tenantsIds.getData()) { + if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { + continue; + } + + Optional tenantProfileConfiguration = tenantProfileCache.get(tenantId).getProfileConfiguration(); + if (tenantProfileConfiguration.isEmpty() || tenantProfileConfiguration.get().getAlarmsTtlDays() == 0) { + continue; + } + + long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getAlarmsTtlDays()); + long expirationTime = System.currentTimeMillis() - ttl; + + long totalRemoved = 0; + while (true) { + PageData toRemove = alarmDao.findAlarmsIdsByEndTsBeforeAndTenantId(expirationTime, tenantId, removalBatchRequest); + toRemove.getData().forEach(alarmId -> { + relationService.deleteEntityRelations(tenantId, alarmId); + Alarm alarm = alarmService.deleteAlarm(tenantId, alarmId).getAlarm(); + ruleEngineEntityActionService.pushEntityActionToRuleEngine(alarm.getOriginator(), alarm, tenantId, null, ActionType.ALARM_DELETE, null); + }); + + totalRemoved += toRemove.getTotalElements(); + if (!toRemove.hasNext()) { + break; + } + } + + if (totalRemoved > 0) { + log.info("Removed {} outdated alarm(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime)); + } + } + + tenantsBatchRequest = tenantsBatchRequest.nextPageLink(); + } while (tenantsIds.hasNext()); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java index 0c21719451..e93a82c7eb 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java @@ -40,7 +40,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { @Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled) { - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + try (Connection conn = getConnection()) { doCleanUp(conn); } catch (SQLException e) { log.error("SQLException occurred during TTL task execution ", e); diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java index 664e01e227..407c88261f 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java @@ -43,7 +43,7 @@ public class EventsCleanUpService extends AbstractCleanUpService { @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled) { - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + try (Connection conn = getConnection()) { doCleanUp(conn); } catch (SQLException e) { log.error("SQLException occurred during TTL task execution ", e); diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java index 9ece0b91a2..ee2d437a22 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java @@ -36,7 +36,7 @@ public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpSe @Scheduled(initialDelayString = "${sql.ttl.ts.execution_interval_ms}", fixedDelayString = "${sql.ttl.ts.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled) { - try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { + try (Connection conn = getConnection()) { doCleanUp(conn); } catch (SQLException e) { log.error("SQLException occurred during TTL task execution ", e); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 7fde122cf6..54721ba2fa 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -273,6 +273,9 @@ sql: enabled: "${SQL_TTL_EDGE_EVENTS_ENABLED:true}" execution_interval_ms: "${SQL_TTL_EDGE_EVENTS_EXECUTION_INTERVAL:86400000}" # Number of milliseconds. The current value corresponds to one day edge_events_ttl: "${SQL_TTL_EDGE_EVENTS_TTL:2628000}" # Number of seconds. The current value corresponds to one month + alarms: + checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours + removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches # Actor system parameters actors: @@ -371,7 +374,10 @@ caffeine: tokensOutdatageTime: timeToLiveInMinutes: 20000 maxSize: 10000 - firmwares: + otaPackages: + timeToLiveInMinutes: 60 + maxSize: 10 + otaPackagesData: timeToLiveInMinutes: 60 maxSize: 10 edges: @@ -497,7 +503,7 @@ audit-log: "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}" "edge": "${AUDIT_LOG_MASK_EDGE:W}" "tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}" - "firmware": "${AUDIT_LOG_MASK_FIRMWARE:W}" + "ota_package": "${AUDIT_LOG_MASK_OTA_PACKAGE:W}" sink: # Type of external sink. possible options: none, elasticsearch type: "${AUDIT_LOG_SINK_TYPE:none}" @@ -650,7 +656,7 @@ transport: bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_port: "${LWM2M_BIND_PORT_BS:5687}" security: - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" + bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_BS:0.0.0.0}" bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" # Only for RPK: Public & Private Key. If the keystore file is missing or not working public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" @@ -749,7 +755,7 @@ queue: sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" consumer-properties-per-topic: - tb_firmware: + tb_ota_package: - key: max.poll.records value: 10 other: @@ -759,7 +765,7 @@ queue: transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" - fw-updates: "${TB_QUEUE_KAFKA_FW_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}" + ota-updates: "${TB_QUEUE_KAFKA_OTA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}" consumer-stats: enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}" @@ -830,10 +836,10 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}" - firmware: - topic: "${TB_QUEUE_CORE_FW_TOPIC:tb_firmware}" - pack-interval-ms: "${TB_QUEUE_CORE_FW_PACK_INTERVAL_MS:60000}" - pack-size: "${TB_QUEUE_CORE_FW_PACK_SIZE:100}" + ota: + topic: "${TB_QUEUE_CORE_OTA_TOPIC:tb_ota_package}" + pack-interval-ms: "${TB_QUEUE_CORE_OTA_PACK_INTERVAL_MS:60000}" + pack-size: "${TB_QUEUE_CORE_OTA_PACK_SIZE:100}" usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java similarity index 64% rename from application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java rename to application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java index ec36be13a5..a11d1b624a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java @@ -25,11 +25,10 @@ import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -41,17 +40,17 @@ import java.util.Collections; import java.util.List; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; -public abstract class BaseFirmwareControllerTest extends AbstractControllerTest { +public abstract class BaseOtaPackageControllerTest extends AbstractControllerTest { - private IdComparator idComparator = new IdComparator<>(); + private IdComparator idComparator = new IdComparator<>(); public static final String TITLE = "My firmware"; private static final String FILE_NAME = "filename.txt"; private static final String VERSION = "v1.0"; private static final String CONTENT_TYPE = "text/plain"; - private static final String CHECKSUM_ALGORITHM = "sha256"; + private static final String CHECKSUM_ALGORITHM = "SHA256"; private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1}); @@ -93,13 +92,13 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testSaveFirmware() throws Exception { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); Assert.assertNotNull(savedFirmwareInfo); Assert.assertNotNull(savedFirmwareInfo.getId()); @@ -112,19 +111,19 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest save(savedFirmwareInfo); - FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); + OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class); Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); } @Test public void testSaveFirmwareData() throws Exception { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); Assert.assertNotNull(savedFirmwareInfo); Assert.assertNotNull(savedFirmwareInfo.getId()); @@ -137,12 +136,12 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest save(savedFirmwareInfo); - FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); + OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class); Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); Assert.assertEquals(FILE_NAME, savedFirmware.getFileName()); Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType()); @@ -150,97 +149,97 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testUpdateFirmwareFromDifferentTenant() throws Exception { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); loginDifferentTenant(); - doPost("/api/firmware", savedFirmwareInfo, FirmwareInfo.class, status().isForbidden()); + doPost("/api/otaPackage", savedFirmwareInfo, OtaPackageInfo.class, status().isForbidden()); deleteDifferentTenant(); } @Test public void testFindFirmwareInfoById() throws Exception { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); - FirmwareInfo foundFirmware = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); + OtaPackageInfo foundFirmware = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class); Assert.assertNotNull(foundFirmware); Assert.assertEquals(savedFirmwareInfo, foundFirmware); } @Test public void testFindFirmwareById() throws Exception { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); - Firmware foundFirmware = doGet("/api/firmware/" + savedFirmwareInfo.getId().getId().toString(), Firmware.class); + OtaPackage foundFirmware = doGet("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString(), OtaPackage.class); Assert.assertNotNull(foundFirmware); Assert.assertEquals(savedFirmware, foundFirmware); } @Test public void testDeleteFirmware() throws Exception { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); - doDelete("/api/firmware/" + savedFirmwareInfo.getId().getId().toString()) + doDelete("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString()) .andExpect(status().isOk()); - doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString()) + doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString()) .andExpect(status().isNotFound()); } @Test public void testFindTenantFirmwares() throws Exception { - List firmwares = new ArrayList<>(); + List otaPackages = new ArrayList<>(); for (int i = 0; i < 165; i++) { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION + i); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); if (i > 100) { MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); - firmwares.add(new FirmwareInfo(savedFirmware)); + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + otaPackages.add(new OtaPackageInfo(savedFirmware)); } else { - firmwares.add(savedFirmwareInfo); + otaPackages.add(savedFirmwareInfo); } } - List loadedFirmwares = new ArrayList<>(); + List loadedFirmwares = new ArrayList<>(); PageLink pageLink = new PageLink(24); - PageData pageData; + PageData pageData; do { - pageData = doGetTypedWithPageLink("/api/firmwares?", + pageData = doGetTypedWithPageLink("/api/otaPackages?", new TypeReference<>() { }, pageLink); loadedFirmwares.addAll(pageData.getData()); @@ -249,80 +248,81 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest } } while (pageData.hasNext()); - Collections.sort(firmwares, idComparator); + Collections.sort(otaPackages, idComparator); Collections.sort(loadedFirmwares, idComparator); - Assert.assertEquals(firmwares, loadedFirmwares); + Assert.assertEquals(otaPackages, loadedFirmwares); } @Test public void testFindTenantFirmwaresByHasData() throws Exception { - List firmwaresWithData = new ArrayList<>(); - List firmwaresWithoutData = new ArrayList<>(); + List otaPackagesWithData = new ArrayList<>(); + List allOtaPackages = new ArrayList<>(); for (int i = 0; i < 165; i++) { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION + i); - FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); if (i > 100) { MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); - firmwaresWithData.add(new FirmwareInfo(savedFirmware)); - } else { - firmwaresWithoutData.add(savedFirmwareInfo); + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + savedFirmwareInfo = new OtaPackageInfo(savedFirmware); + otaPackagesWithData.add(savedFirmwareInfo); } + + allOtaPackages.add(savedFirmwareInfo); } - List loadedFirmwaresWithData = new ArrayList<>(); + List loadedOtaPackagesWithData = new ArrayList<>(); PageLink pageLink = new PageLink(24); - PageData pageData; + PageData pageData; do { - pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/true?", + pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE?", new TypeReference<>() { }, pageLink); - loadedFirmwaresWithData.addAll(pageData.getData()); + loadedOtaPackagesWithData.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - List loadedFirmwaresWithoutData = new ArrayList<>(); + List allLoadedOtaPackages = new ArrayList<>(); pageLink = new PageLink(24); do { - pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/false?", + pageData = doGetTypedWithPageLink("/api/otaPackages?", new TypeReference<>() { }, pageLink); - loadedFirmwaresWithoutData.addAll(pageData.getData()); + allLoadedOtaPackages.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(firmwaresWithData, idComparator); - Collections.sort(firmwaresWithoutData, idComparator); - Collections.sort(loadedFirmwaresWithData, idComparator); - Collections.sort(loadedFirmwaresWithoutData, idComparator); + Collections.sort(otaPackagesWithData, idComparator); + Collections.sort(allOtaPackages, idComparator); + Collections.sort(loadedOtaPackagesWithData, idComparator); + Collections.sort(allLoadedOtaPackages, idComparator); - Assert.assertEquals(firmwaresWithData, loadedFirmwaresWithData); - Assert.assertEquals(firmwaresWithoutData, loadedFirmwaresWithoutData); + Assert.assertEquals(otaPackagesWithData, loadedOtaPackagesWithData); + Assert.assertEquals(allOtaPackages, allLoadedOtaPackages); } - private FirmwareInfo save(FirmwareInfo firmwareInfo) throws Exception { - return doPost("/api/firmware", firmwareInfo, FirmwareInfo.class); + private OtaPackageInfo save(OtaPackageInfo firmwareInfo) throws Exception { + return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); } - protected Firmware savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { + protected OtaPackage savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); postRequest.file(content); setJwtToken(postRequest); - return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), Firmware.class); + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackage.class); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/OtaPackageControllerSqlTest.java similarity index 82% rename from application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java rename to application/src/test/java/org/thingsboard/server/controller/sql/OtaPackageControllerSqlTest.java index a0e4a838ca..92bd9ba9c6 100644 --- a/application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/sql/OtaPackageControllerSqlTest.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.controller.sql; -import org.thingsboard.server.controller.BaseFirmwareControllerTest; +import org.thingsboard.server.controller.BaseOtaPackageControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; @DaoSqlTest -public class FirmwareControllerSqlTest extends BaseFirmwareControllerTest { +public class OtaPackageControllerSqlTest extends BaseOtaPackageControllerTest { } diff --git a/application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java index 464ca5c3cf..62facbb424 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java @@ -19,17 +19,24 @@ import com.datastax.oss.driver.api.core.uuid.Uuids; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.EntityInfo; +import org.thingsboard.server.common.data.OtaPackage; 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.TenantProfile; import org.thingsboard.server.common.data.User; +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.security.Authority; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -109,6 +116,64 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { .andExpect(status().isOk()); } + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception { + loginSysAdmin(); + long limit = 1; + EntityInfo defaultTenantProfileInfo = doGet("/api/tenantProfileInfo/default", EntityInfo.class); + TenantProfile defaultTenantProfile = doGet("/api/tenantProfile/" + defaultTenantProfileInfo.getId().getId().toString(), TenantProfile.class); + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(limit).build()); + doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class); + + loginTenantAdmin(); + + Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); + + createResource("test", DEFAULT_FILE_NAME); + + Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); + + try { + thrown.expect(DataValidationException.class); + thrown.expectMessage(String.format("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit)); + createResource("test1", 1 + DEFAULT_FILE_NAME); + } finally { + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(0).build()); + loginSysAdmin(); + doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class); + } + } + + @Test + public void sumDataSizeByTenantId() throws ThingsboardException { + Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); + + createResource("test", DEFAULT_FILE_NAME); + Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId)); + + int maxSumDataSize = 8; + + for (int i = 2; i <= maxSumDataSize; i++) { + createResource("test" + i, i + DEFAULT_FILE_NAME); + Assert.assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId)); + } + + Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId)); + } + + private TbResource createResource(String title, String filename) throws ThingsboardException { + TbResource resource = new TbResource(); + resource.setTenantId(tenantId); + resource.setTitle(title); + resource.setResourceType(ResourceType.JKS); + resource.setFileName(filename); + resource.setData("1"); + return resourceService.saveResource(resource); + } + @Test public void testSaveTbResource() throws Exception { TbResource resource = new TbResource(); 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 90b926e78a..6db6e24f91 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 @@ -60,6 +60,53 @@ import java.util.concurrent.ScheduledExecutorService; @DaoSqlTest public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { + protected final String TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.0/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrap\": {\n" + + " \"servers\": {\n" + + " \"binding\": \"U\",\n" + + " \"shortId\": 123,\n" + + " \"lifetime\": 300,\n" + + " \"notifIfDisabled\": true,\n" + + " \"defaultMinPeriod\": 1\n" + + " },\n" + + " \"lwm2mServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5686,\n" + + " \"serverId\": 123,\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " },\n" + + " \"bootstrapServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5687,\n" + + " \"serverId\": 111,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " }\n" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + protected DeviceProfile deviceProfile; protected ScheduledExecutorService executor; protected TbTestWebSocketClient wsClient; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index c82845f20c..7d173e61d9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -22,6 +22,7 @@ import org.junit.Assert; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -36,7 +37,6 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; import java.util.Collections; import java.util.List; @@ -46,60 +46,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { - protected final String TRANSPORT_CONFIGURATION = "{\n" + - " \"type\": \"LWM2M\",\n" + - " \"observeAttr\": {\n" + - " \"keyName\": {\n" + - " \"/3_1.0/0/9\": \"batteryLevel\"\n" + - " },\n" + - " \"observe\": [],\n" + - " \"attribute\": [\n" + - " ],\n" + - " \"telemetry\": [\n" + - " \"/3_1.0/0/9\"\n" + - " ],\n" + - " \"attributeLwm2m\": {}\n" + - " },\n" + - " \"bootstrap\": {\n" + - " \"servers\": {\n" + - " \"binding\": \"UQ\",\n" + - " \"shortId\": 123,\n" + - " \"lifetime\": 300,\n" + - " \"notifIfDisabled\": true,\n" + - " \"defaultMinPeriod\": 1\n" + - " },\n" + - " \"lwm2mServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5685,\n" + - " \"serverId\": 123,\n" + - " \"securityMode\": \"NO_SEC\",\n" + - " \"serverPublicKey\": \"\",\n" + - " \"bootstrapServerIs\": false,\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + - " },\n" + - " \"bootstrapServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5687,\n" + - " \"serverId\": 111,\n" + - " \"securityMode\": \"NO_SEC\",\n" + - " \"serverPublicKey\": \"\",\n" + - " \"bootstrapServerIs\": true,\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + - " }\n" + - " },\n" + - " \"clientLwM2mSettings\": {\n" + - " \"clientOnlyObserveAfterConnect\": 1\n" + - " }\n" + - "}"; - - private final int port = 5685; - private final Security security = noSec("coap://localhost:" + port, 123); - private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_PORT", Integer.toString(port)); + private final int PORT = 5685; + private final Security SECURITY = noSec("coap://localhost:" + PORT, 123); + private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); + private final String ENDPOINT = "deviceAEndpoint"; @NotNull - private Device createDevice(String deviceAEndpoint) throws Exception { + private Device createDevice() throws Exception { Device device = new Device(); device.setName("Device A"); device.setDeviceProfileId(deviceProfile.getId()); @@ -114,7 +67,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { LwM2MCredentials noSecCredentials = new LwM2MCredentials(); NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(deviceAEndpoint); + clientCredentials.setEndpoint(ENDPOINT); noSecCredentials.setClient(clientCredentials); deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); @@ -125,9 +78,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { public void testConnectAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); - String deviceAEndpoint = "deviceAEndpoint"; - - Device device = createDevice(deviceAEndpoint); + Device device = createDevice(); SingleEntityFilter sef = new SingleEntityFilter(); sef.setSingleEntity(device.getId()); @@ -144,8 +95,8 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { wsClient.waitForReply(); wsClient.registerWaitForUpdate(); - LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); - client.init(security, coapConfig); + LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT); + client.init(SECURITY, COAP_CONFIG); String msg = wsClient.waitForUpdate(); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java index 18749cfee5..0c13c57441 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -47,54 +47,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { - protected final String TRANSPORT_CONFIGURATION = "{\n" + - " \"type\": \"LWM2M\",\n" + - " \"observeAttr\": {\n" + - " \"keyName\": {\n" + - " \"/3_1.0/0/9\": \"batteryLevel\"\n" + - " },\n" + - " \"observe\": [],\n" + - " \"attribute\": [\n" + - " ],\n" + - " \"telemetry\": [\n" + - " \"/3_1.0/0/9\"\n" + - " ],\n" + - " \"attributeLwm2m\": {}\n" + - " },\n" + - " \"bootstrap\": {\n" + - " \"servers\": {\n" + - " \"binding\": \"UQ\",\n" + - " \"shortId\": 123,\n" + - " \"lifetime\": 300,\n" + - " \"notifIfDisabled\": true,\n" + - " \"defaultMinPeriod\": 1\n" + - " },\n" + - " \"lwm2mServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5686,\n" + - " \"serverId\": 123,\n" + - " \"serverPublicKey\": \"\",\n" + - " \"bootstrapServerIs\": false,\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + - " },\n" + - " \"bootstrapServer\": {\n" + - " \"host\": \"localhost\",\n" + - " \"port\": 5687,\n" + - " \"serverId\": 111,\n" + - " \"securityMode\": \"NO_SEC\",\n" + - " \"serverPublicKey\": \"\",\n" + - " \"bootstrapServerIs\": true,\n" + - " \"clientHoldOffTime\": 1,\n" + - " \"bootstrapServerAccountTimeout\": 0\n" + - " }\n" + - " },\n" + - " \"clientLwM2mSettings\": {\n" + - " \"clientOnlyObserveAfterConnect\": 1\n" + - " }\n" + - "}"; - - private final int port = 5686; private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port)); private final String endpoint = "deviceAEndpoint"; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 8a17b6e3c9..4ab03fa6c0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -17,40 +17,37 @@ package org.thingsboard.server.transport.lwm2m.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.config.NetworkConfig; -import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.core.observe.ObservationStore; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; -import org.eclipse.californium.scandium.dtls.ClientHandshaker; -import org.eclipse.californium.scandium.dtls.DTLSSession; -import org.eclipse.californium.scandium.dtls.HandshakeException; -import org.eclipse.californium.scandium.dtls.Handshaker; -import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker; -import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker; -import org.eclipse.californium.scandium.dtls.ServerHandshaker; -import org.eclipse.californium.scandium.dtls.SessionAdapter; import org.eclipse.leshan.client.californium.LeshanClient; import org.eclipse.leshan.client.californium.LeshanClientBuilder; import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.observer.LwM2mClientObserver; +import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.LwM2mId; import org.eclipse.leshan.core.ResponseCode; -import org.eclipse.leshan.core.californium.DefaultEndpointFactory; +import org.eclipse.leshan.core.californium.EndpointFactory; +import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.StaticModel; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; -import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.UpdateRequest; +import java.io.IOException; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; @@ -67,7 +64,7 @@ public class LwM2MTestClient { private final String endpoint; private LeshanClient client; - public void init(Security security, NetworkConfig coapConfig) { + public void init(Security security, NetworkConfig coapConfig) throws InvalidDDFFileException, IOException { String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; List models = new ArrayList<>(); for (String resourceName : resources) { @@ -76,82 +73,51 @@ public class LwM2MTestClient { LwM2mModel model = new StaticModel(models); ObjectsInitializer initializer = new ObjectsInitializer(model); initializer.setInstancesForObject(SECURITY, security); - initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); + initializer.setInstancesForObject(SERVER, new Server(123, 300)); initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); + initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setRecommendedCipherSuitesOnly(true); + dtlsConfig.setClientOnly(); DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); engineFactory.setReconnectOnUpdate(false); engineFactory.setResumeOnConnect(true); - DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) { + EndpointFactory endpointFactory = new EndpointFactory() { + @Override - protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { - - return new DTLSConnector(dtlsConfig) { - @Override - protected void onInitializeHandshaker(Handshaker handshaker) { - handshaker.addSessionListener(new SessionAdapter() { - - @Override - public void handshakeStarted(Handshaker handshaker) throws HandshakeException { - if (handshaker instanceof ServerHandshaker) { - log.info("DTLS Full Handshake initiated by server : STARTED ..."); - } else if (handshaker instanceof ResumingServerHandshaker) { - log.info("DTLS abbreviated Handshake initiated by server : STARTED ..."); - } else if (handshaker instanceof ClientHandshaker) { - log.info("DTLS Full Handshake initiated by client : STARTED ..."); - } else if (handshaker instanceof ResumingClientHandshaker) { - log.info("DTLS abbreviated Handshake initiated by client : STARTED ..."); - } - } - - @Override - public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession) - throws HandshakeException { - if (handshaker instanceof ServerHandshaker) { - log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker); - } else if (handshaker instanceof ResumingServerHandshaker) { - log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker); - } else if (handshaker instanceof ClientHandshaker) { - log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker); - } else if (handshaker instanceof ResumingClientHandshaker) { - log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker); - } - } - - @Override - public void handshakeFailed(Handshaker handshaker, Throwable error) { - /** get cause */ - String cause; - if (error != null) { - if (error.getMessage() != null) { - cause = error.getMessage(); - } else { - cause = error.getClass().getName(); - } - } else { - cause = "unknown cause"; - } - - if (handshaker instanceof ServerHandshaker) { - log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause); - } else if (handshaker instanceof ResumingServerHandshaker) { - log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause); - } else if (handshaker instanceof ClientHandshaker) { - log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause); - } else if (handshaker instanceof ResumingClientHandshaker) { - log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause); - } - } - }); - } - }; + public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig, + ObservationStore store) { + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + builder.setInetSocketAddress(address); + builder.setNetworkConfig(coapConfig); + return builder.build(); + } + + @Override + public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig, + ObservationStore store) { + CoapEndpoint.Builder builder = new CoapEndpoint.Builder(); + DtlsConnectorConfig.Builder dtlsConfigBuilder = new DtlsConnectorConfig.Builder(dtlsConfig); + + // tricks to be able to change psk information on the fly +// AdvancedPskStore pskStore = dtlsConfig.getAdvancedPskStore(); +// if (pskStore != null) { +// PskPublicInformation identity = pskStore.getIdentity(null, null); +// SecretKey key = pskStore +// .requestPskSecretResult(ConnectionId.EMPTY, null, identity, null, null, null).getSecret(); +// singlePSKStore = new SinglePSKStore(identity, key); +// dtlsConfigBuilder.setAdvancedPskStore(singlePSKStore); +// } + builder.setConnector(new DTLSConnector(dtlsConfigBuilder.build())); + builder.setNetworkConfig(coapConfig); + return builder.build(); } }; + LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); builder.setLocalAddress("0.0.0.0", 11000); builder.setObjects(initializer.createAll()); @@ -246,6 +212,11 @@ public class LwM2MTestClient { public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); } + + @Override + public void onUnexpectedError(Throwable unexpectedError) { + + } }; this.client.addObserver(observer); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index 4512a94a27..9c9741b0a3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -97,7 +97,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } @Override - public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) { + public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) { log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); switch (resourceid) { @@ -112,7 +112,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl fireResourcesChange(resourceid); return WriteResponse.success(); default: - return super.write(identity, resourceid, value); + return super.write(identity, replace, resourceid, value); } } diff --git a/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks b/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks index 7cc58589b7..a6c9ae7fae 100644 Binary files a/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks and b/application/src/test/resources/lwm2m/credentials/clientKeyStore.jks differ diff --git a/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks b/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks index f1f03005e1..fc541a3b18 100644 Binary files a/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks and b/application/src/test/resources/lwm2m/credentials/serverKeyStore.jks differ diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/ota/CaffeineOtaPackageCache.java similarity index 80% rename from common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCache.java rename to common/cache/src/main/java/org/thingsboard/server/cache/ota/CaffeineOtaPackageCache.java index 1acb09b28e..8b1f0804f3 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/ota/CaffeineOtaPackageCache.java @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.cache.firmware; +package org.thingsboard.server.cache.ota; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; -import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; +import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE; +import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_DATA_CACHE; @Service @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) @RequiredArgsConstructor -public class CaffeineFirmwareCache implements FirmwareDataCache { +public class CaffeineOtaPackageCache implements OtaPackageDataCache { private final CacheManager cacheManager; @@ -36,7 +37,7 @@ public class CaffeineFirmwareCache implements FirmwareDataCache { @Override public byte[] get(String key, int chunkSize, int chunk) { - byte[] data = cacheManager.getCache(FIRMWARE_CACHE).get(key, byte[].class); + byte[] data = cacheManager.getCache(OTA_PACKAGE_DATA_CACHE).get(key, byte[].class); if (chunkSize < 1) { return data; @@ -58,11 +59,11 @@ public class CaffeineFirmwareCache implements FirmwareDataCache { @Override public void put(String key, byte[] value) { - cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value); + cacheManager.getCache(OTA_PACKAGE_DATA_CACHE).putIfAbsent(key, value); } @Override public void evict(String key) { - cacheManager.getCache(FIRMWARE_CACHE).evict(key); + cacheManager.getCache(OTA_PACKAGE_DATA_CACHE).evict(key); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/ota/OtaPackageDataCache.java similarity index 82% rename from common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java rename to common/cache/src/main/java/org/thingsboard/server/cache/ota/OtaPackageDataCache.java index 99f2f0d521..77057406c2 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/ota/OtaPackageDataCache.java @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.cache.firmware; +package org.thingsboard.server.cache.ota; -public interface FirmwareDataCache { +public interface OtaPackageDataCache { byte[] get(String key); @@ -25,8 +25,8 @@ public interface FirmwareDataCache { void evict(String key); - default boolean has(String firmwareId) { - byte[] data = get(firmwareId, 1, 0); + default boolean has(String otaPackageId) { + byte[] data = get(otaPackageId, 1, 0); return data != null && data.length > 0; } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareDataCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/ota/RedisOtaPackageDataCache.java similarity index 75% rename from common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareDataCache.java rename to common/cache/src/main/java/org/thingsboard/server/cache/ota/RedisOtaPackageDataCache.java index 08dd6facc2..c2c3bc34a8 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareDataCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/ota/RedisOtaPackageDataCache.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.cache.firmware; +package org.thingsboard.server.cache.ota; import lombok.RequiredArgsConstructor; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -21,12 +21,13 @@ import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; -import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; +import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE; +import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_DATA_CACHE; @Service @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @RequiredArgsConstructor -public class RedisFirmwareDataCache implements FirmwareDataCache { +public class RedisOtaPackageDataCache implements OtaPackageDataCache { private final RedisConnectionFactory redisConnectionFactory; @@ -39,30 +40,30 @@ public class RedisFirmwareDataCache implements FirmwareDataCache { public byte[] get(String key, int chunkSize, int chunk) { try (RedisConnection connection = redisConnectionFactory.getConnection()) { if (chunkSize == 0) { - return connection.get(toFirmwareCacheKey(key)); + return connection.get(toOtaPackageCacheKey(key)); } int startIndex = chunkSize * chunk; int endIndex = startIndex + chunkSize - 1; - return connection.getRange(toFirmwareCacheKey(key), startIndex, endIndex); + return connection.getRange(toOtaPackageCacheKey(key), startIndex, endIndex); } } @Override public void put(String key, byte[] value) { try (RedisConnection connection = redisConnectionFactory.getConnection()) { - connection.set(toFirmwareCacheKey(key), value); + connection.set(toOtaPackageCacheKey(key), value); } } @Override public void evict(String key) { try (RedisConnection connection = redisConnectionFactory.getConnection()) { - connection.del(toFirmwareCacheKey(key)); + connection.del(toOtaPackageCacheKey(key)); } } - private byte[] toFirmwareCacheKey(String key) { - return String.format("%s::%s", FIRMWARE_CACHE, key).getBytes(); + private byte[] toOtaPackageCacheKey(String key) { + return String.format("%s::%s", OTA_PACKAGE_DATA_CACHE, key).getBytes(); } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index b4692ec5bf..761211f32c 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; 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.security.DeviceCredentials; @@ -63,9 +64,9 @@ public interface DeviceService { PageData findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); - PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink); + PageData findDevicesByTenantIdAndTypeAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type, PageLink pageLink); - PageData findDevicesByTenantIdAndTypeAndEmptySoftware(TenantId tenantId, String type, PageLink pageLink); + Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType); PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java deleted file mode 100644 index eeaafbd777..0000000000 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.firmware; - -import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.FirmwareInfo; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; -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 java.nio.ByteBuffer; - -public interface FirmwareService { - - FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo); - - Firmware saveFirmware(Firmware firmware); - - String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data); - - Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); - - FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId); - - ListenableFuture findFirmwareInfoByIdAsync(TenantId tenantId, FirmwareId firmwareId); - - PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink); - - PageData findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink); - - void deleteFirmware(TenantId tenantId, FirmwareId firmwareId); - - void deleteFirmwaresByTenantId(TenantId tenantId); -} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java index 3f9411d0aa..158c3911b4 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Service.java @@ -16,20 +16,30 @@ package org.thingsboard.server.dao.oauth2; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; import java.util.List; import java.util.UUID; public interface OAuth2Service { - List getOAuth2Clients(String domainScheme, String domainName); + List getOAuth2Clients(String domainScheme, String domainName, String pkgName); + @Deprecated void saveOAuth2Params(OAuth2ClientsParams oauth2Params); + @Deprecated OAuth2ClientsParams findOAuth2Params(); - OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id); + void saveOAuth2Info(OAuth2Info oauth2Info); - List findAllClientRegistrationInfos(); + OAuth2Info findOAuth2Info(); + + OAuth2Registration findRegistration(UUID id); + + List findAllRegistrations(); + + String findCallbackUrlScheme(UUID registrationId, String pkgName); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java new file mode 100644 index 0000000000..fea29681c1 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java @@ -0,0 +1,54 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.ota; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.OtaPackageId; +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 java.nio.ByteBuffer; + +public interface OtaPackageService { + + OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo); + + OtaPackage saveOtaPackage(OtaPackage otaPackage); + + String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data); + + OtaPackage findOtaPackageById(TenantId tenantId, OtaPackageId otaPackageId); + + OtaPackageInfo findOtaPackageInfoById(TenantId tenantId, OtaPackageId otaPackageId); + + ListenableFuture findOtaPackageInfoByIdAsync(TenantId tenantId, OtaPackageId otaPackageId); + + PageData findTenantOtaPackagesByTenantId(TenantId tenantId, PageLink pageLink); + + PageData findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink); + + void deleteOtaPackage(TenantId tenantId, OtaPackageId otaPackageId); + + void deleteOtaPackagesByTenantId(TenantId tenantId); + + long sumDataSizeByTenantId(TenantId tenantId); +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java index 802628ff2c..1694c89bae 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java @@ -49,5 +49,5 @@ public interface ResourceService { void deleteResourcesByTenantId(TenantId tenantId); - + long sumDataSizeByTenantId(TenantId tenantId); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index ce4576705b..ced7a64a0f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -29,5 +29,6 @@ public class CacheConstants { public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String ATTRIBUTES_CACHE = "attributes"; public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime"; - public static final String FIRMWARE_CACHE = "firmwares"; + public static final String OTA_PACKAGE_CACHE = "otaPackages"; + public static final String OTA_PACKAGE_DATA_CACHE = "otaPackagesData"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 62459fc0ad..002cbbb733 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -66,6 +66,7 @@ public class DataConstants { public static final String TIMESERIES_DELETED = "TIMESERIES_DELETED"; public static final String ALARM_ACK = "ALARM_ACK"; public static final String ALARM_CLEAR = "ALARM_CLEAR"; + public static final String ALARM_DELETE = "ALARM_DELETE"; public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; public static final String ENTITY_ASSIGNED_TO_TENANT = "ENTITY_ASSIGNED_TO_TENANT"; public static final String PROVISION_SUCCESS = "PROVISION_SUCCESS"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index bce3ba703f..9abc619b48 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.NoXss; @@ -32,7 +32,7 @@ import java.io.IOException; @EqualsAndHashCode(callSuper = true) @Slf4j -public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, HasFirmware { +public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, HasOtaPackage { private static final long serialVersionUID = 2807343040519543363L; @@ -49,8 +49,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen @JsonIgnore private byte[] deviceDataBytes; - private FirmwareId firmwareId; - private FirmwareId softwareId; + private OtaPackageId firmwareId; + private OtaPackageId softwareId; public Device() { super(); @@ -167,19 +167,19 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen return getName(); } - public FirmwareId getFirmwareId() { + public OtaPackageId getFirmwareId() { return firmwareId; } - public void setFirmwareId(FirmwareId firmwareId) { + public void setFirmwareId(OtaPackageId firmwareId) { this.firmwareId = firmwareId; } - public FirmwareId getSoftwareId() { + public OtaPackageId getSoftwareId() { return softwareId; } - public void setSoftwareId(FirmwareId softwareId) { + public void setSoftwareId(OtaPackageId softwareId) { this.softwareId = softwareId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index e35fbb84a0..13c4692976 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -23,7 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.NoXss; @@ -37,7 +37,7 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn @Data @EqualsAndHashCode(callSuper = true) @Slf4j -public class DeviceProfile extends SearchTextBased implements HasName, HasTenantId, HasFirmware { +public class DeviceProfile extends SearchTextBased implements HasName, HasTenantId, HasOtaPackage { private TenantId tenantId; @NoXss @@ -60,9 +60,9 @@ public class DeviceProfile extends SearchTextBased implements H @NoXss private String provisionDeviceKey; - private FirmwareId firmwareId; + private OtaPackageId firmwareId; - private FirmwareId softwareId; + private OtaPackageId softwareId; public DeviceProfile() { super(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index d3508d42dd..cf6c6fd9a7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, FIRMWARE, EDGE; + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, OTA_PACKAGE, EDGE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasFirmware.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasOtaPackage.java similarity index 80% rename from common/data/src/main/java/org/thingsboard/server/common/data/HasFirmware.java rename to common/data/src/main/java/org/thingsboard/server/common/data/HasOtaPackage.java index ae05829092..09320e13d0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/HasFirmware.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasOtaPackage.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.common.data; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; -public interface HasFirmware { +public interface HasOtaPackage { - FirmwareId getFirmwareId(); + OtaPackageId getFirmwareId(); - FirmwareId getSoftwareId(); + OtaPackageId getSoftwareId(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java similarity index 76% rename from common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java rename to common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java index b4155a578a..3506aaea75 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java @@ -17,28 +17,28 @@ package org.thingsboard.server.common.data; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import java.nio.ByteBuffer; @Data @EqualsAndHashCode(callSuper = true) -public class Firmware extends FirmwareInfo { +public class OtaPackage extends OtaPackageInfo { private static final long serialVersionUID = 3091601761339422546L; private transient ByteBuffer data; - public Firmware() { + public OtaPackage() { super(); } - public Firmware(FirmwareId id) { + public OtaPackage(OtaPackageId id) { super(id); } - public Firmware(Firmware firmware) { - super(firmware); - this.data = firmware.getData(); + public OtaPackage(OtaPackage otaPackage) { + super(otaPackage); + this.data = otaPackage.getData(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java similarity index 55% rename from common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java index 33b529e303..f27c90c20b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java @@ -19,24 +19,25 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; @Slf4j @Data @EqualsAndHashCode(callSuper = true) -public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { +public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { private static final long serialVersionUID = 3168391583570815419L; private TenantId tenantId; private DeviceProfileId deviceProfileId; - private FirmwareType type; + private OtaPackageType type; private String title; private String version; + private String url; private boolean hasData; private String fileName; private String contentType; @@ -45,27 +46,28 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo private Long dataSize; - public FirmwareInfo() { + public OtaPackageInfo() { super(); } - public FirmwareInfo(FirmwareId id) { + public OtaPackageInfo(OtaPackageId id) { super(id); } - public FirmwareInfo(FirmwareInfo firmwareInfo) { - super(firmwareInfo); - this.tenantId = firmwareInfo.getTenantId(); - this.deviceProfileId = firmwareInfo.getDeviceProfileId(); - this.type = firmwareInfo.getType(); - this.title = firmwareInfo.getTitle(); - this.version = firmwareInfo.getVersion(); - this.hasData = firmwareInfo.isHasData(); - this.fileName = firmwareInfo.getFileName(); - this.contentType = firmwareInfo.getContentType(); - this.checksumAlgorithm = firmwareInfo.getChecksumAlgorithm(); - this.checksum = firmwareInfo.getChecksum(); - this.dataSize = firmwareInfo.getDataSize(); + public OtaPackageInfo(OtaPackageInfo otaPackageInfo) { + super(otaPackageInfo); + this.tenantId = otaPackageInfo.getTenantId(); + this.deviceProfileId = otaPackageInfo.getDeviceProfileId(); + this.type = otaPackageInfo.getType(); + this.title = otaPackageInfo.getTitle(); + this.version = otaPackageInfo.getVersion(); + this.url = otaPackageInfo.getUrl(); + this.hasData = otaPackageInfo.isHasData(); + this.fileName = otaPackageInfo.getFileName(); + this.contentType = otaPackageInfo.getContentType(); + this.checksumAlgorithm = otaPackageInfo.getChecksumAlgorithm(); + this.checksum = otaPackageInfo.getChecksum(); + this.dataSize = otaPackageInfo.getDataSize(); } @Override @@ -78,4 +80,9 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo public String getName() { return title; } + + @JsonIgnore + public boolean hasUrl() { + return StringUtils.isNotEmpty(url); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index a0fefea6cc..05d22af09f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.validation.NoXss; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.Optional; import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo.mapper; @@ -92,6 +93,13 @@ public class TenantProfile extends SearchTextBased implements H } } + @JsonIgnore + public Optional getProfileConfiguration() { + return Optional.ofNullable(getProfileData().getConfiguration()) + .filter(profileConfiguration -> profileConfiguration instanceof DefaultTenantProfileConfiguration) + .map(profileConfiguration -> (DefaultTenantProfileConfiguration) profileConfiguration); + } + public TenantProfileData createDefaultTenantProfileData() { TenantProfileData tpd = new TenantProfileData(); tpd.setConfiguration(new DefaultTenantProfileConfiguration()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 2594c73ebe..489c45f68d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -39,6 +39,7 @@ public enum ActionType { RELATIONS_DELETED(false), ALARM_ACK(false), ALARM_CLEAR(false), + ALARM_DELETE(false), LOGIN(false), LOGOUT(false), LOCKOUT(false), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/exception/ApiUsageLimitsExceededException.java b/common/data/src/main/java/org/thingsboard/server/common/data/exception/ApiUsageLimitsExceededException.java new file mode 100644 index 0000000000..fb5c4dffeb --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/exception/ApiUsageLimitsExceededException.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.exception; + +public class ApiUsageLimitsExceededException extends RuntimeException { + public ApiUsageLimitsExceededException(String message) { + super(message); + } + + public ApiUsageLimitsExceededException() { + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index df2846eead..a4b2327c75 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -71,8 +71,8 @@ public class EntityIdFactory { return new ApiUsageStateId(uuid); case TB_RESOURCE: return new TbResourceId(uuid); - case FIRMWARE: - return new FirmwareId(uuid); + case OTA_PACKAGE: + return new OtaPackageId(uuid); case EDGE: return new EdgeId(uuid); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2DomainId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2DomainId.java new file mode 100644 index 0000000000..220232f16d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2DomainId.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class OAuth2DomainId extends UUIDBased { + + @JsonCreator + public OAuth2DomainId(@JsonProperty("id") UUID id) { + super(id); + } + + public static OAuth2DomainId fromString(String oauth2DomainId) { + return new OAuth2DomainId(UUID.fromString(oauth2DomainId)); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2MobileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2MobileId.java new file mode 100644 index 0000000000..934d1f72a6 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2MobileId.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class OAuth2MobileId extends UUIDBased { + + @JsonCreator + public OAuth2MobileId(@JsonProperty("id") UUID id) { + super(id); + } + + public static OAuth2MobileId fromString(String oauth2MobileId) { + return new OAuth2MobileId(UUID.fromString(oauth2MobileId)); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ParamsId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ParamsId.java new file mode 100644 index 0000000000..3aa0c5bb3d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ParamsId.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class OAuth2ParamsId extends UUIDBased { + + @JsonCreator + public OAuth2ParamsId(@JsonProperty("id") UUID id) { + super(id); + } + + public static OAuth2ParamsId fromString(String oauth2ParamsId) { + return new OAuth2ParamsId(UUID.fromString(oauth2ParamsId)); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java new file mode 100644 index 0000000000..0019ef1a04 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2RegistrationId.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.UUID; + +public class OAuth2RegistrationId extends UUIDBased { + + @JsonCreator + public OAuth2RegistrationId(@JsonProperty("id") UUID id) { + super(id); + } + + public static OAuth2RegistrationId fromString(String oauth2RegistrationId) { + return new OAuth2RegistrationId(UUID.fromString(oauth2RegistrationId)); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java similarity index 79% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java index 3cdee53f58..0442792238 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java @@ -22,23 +22,23 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class FirmwareId extends UUIDBased implements EntityId { +public class OtaPackageId extends UUIDBased implements EntityId { private static final long serialVersionUID = 1L; @JsonCreator - public FirmwareId(@JsonProperty("id") UUID id) { + public OtaPackageId(@JsonProperty("id") UUID id) { super(id); } - public static FirmwareId fromString(String firmwareId) { - return new FirmwareId(UUID.fromString(firmwareId)); + public static OtaPackageId fromString(String firmwareId) { + return new OtaPackageId(UUID.fromString(firmwareId)); } @JsonIgnore @Override public EntityType getEntityType() { - return EntityType.FIRMWARE; + return EntityType.OTA_PACKAGE; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationId.java similarity index 89% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationId.java index 88d4245dc7..616fc3b29c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationId.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.id; +package org.thingsboard.server.common.data.id.deprecated; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.id.UUIDBased; import java.util.UUID; +@Deprecated public class OAuth2ClientRegistrationId extends UUIDBased { @JsonCreator diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationInfoId.java similarity index 89% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationInfoId.java index 2e34eaf5ac..6ba959318c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OAuth2ClientRegistrationInfoId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/deprecated/OAuth2ClientRegistrationInfoId.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.id; +package org.thingsboard.server.common.data.id.deprecated; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.id.UUIDBased; import java.util.UUID; +@Deprecated public class OAuth2ClientRegistrationInfoId extends UUIDBased { @JsonCreator diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java index f75fb2aaa7..b46f23484f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationTemplate.java @@ -20,10 +20,8 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.HasName; -import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; -import org.thingsboard.server.common.data.id.TenantId; import java.util.List; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java new file mode 100644 index 0000000000..0dee447023 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Domain.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.OAuth2DomainId; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString +@NoArgsConstructor +public class OAuth2Domain extends BaseData { + + private OAuth2ParamsId oauth2ParamsId; + private String domainName; + private SchemeType domainScheme; + + public OAuth2Domain(OAuth2Domain domain) { + super(domain); + this.oauth2ParamsId = domain.oauth2ParamsId; + this.domainName = domain.domainName; + this.domainScheme = domain.domainScheme; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java new file mode 100644 index 0000000000..9d9bd939da --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2DomainInfo.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@EqualsAndHashCode +@Data +@ToString +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class OAuth2DomainInfo { + private SchemeType scheme; + private String name; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java new file mode 100644 index 0000000000..72f4b06161 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Info.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.*; + +import java.util.List; + +@EqualsAndHashCode +@Data +@ToString +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2Info { + private boolean enabled; + private List oauth2ParamsInfos; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java new file mode 100644 index 0000000000..b3249a1ed5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Mobile.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.OAuth2MobileId; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString +@NoArgsConstructor +public class OAuth2Mobile extends BaseData { + + private OAuth2ParamsId oauth2ParamsId; + private String pkgName; + private String callbackUrlScheme; + + public OAuth2Mobile(OAuth2Mobile mobile) { + super(mobile); + this.oauth2ParamsId = mobile.oauth2ParamsId; + this.pkgName = mobile.pkgName; + this.callbackUrlScheme = mobile.callbackUrlScheme; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java new file mode 100644 index 0000000000..18fbd49dcd --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2MobileInfo.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@EqualsAndHashCode +@Data +@ToString +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class OAuth2MobileInfo { + private String pkgName; + private String callbackUrlScheme; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Params.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Params.java new file mode 100644 index 0000000000..570fe6cb20 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Params.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.id.TenantId; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString +@NoArgsConstructor +public class OAuth2Params extends BaseData { + + private boolean enabled; + private TenantId tenantId; + + public OAuth2Params(OAuth2Params oauth2Params) { + super(oauth2Params); + this.enabled = oauth2Params.enabled; + this.tenantId = oauth2Params.tenantId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java new file mode 100644 index 0000000000..1a1d729d6b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ParamsInfo.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.List; + +@EqualsAndHashCode +@Data +@ToString +@Builder(toBuilder = true) +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2ParamsInfo { + + private List domainInfos; + private List mobileInfos; + private List clientRegistrations; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java new file mode 100644 index 0000000000..5120e3202f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2Registration.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +@ToString(exclude = {"clientSecret"}) +@NoArgsConstructor +public class OAuth2Registration extends SearchTextBasedWithAdditionalInfo implements HasName { + + private OAuth2ParamsId oauth2ParamsId; + private OAuth2MapperConfig mapperConfig; + private String clientId; + private String clientSecret; + private String authorizationUri; + private String accessTokenUri; + private List scope; + private String userInfoUri; + private String userNameAttributeName; + private String jwkSetUri; + private String clientAuthenticationMethod; + private String loginButtonLabel; + private String loginButtonIcon; + + public OAuth2Registration(OAuth2Registration registration) { + super(registration); + this.oauth2ParamsId = registration.oauth2ParamsId; + this.mapperConfig = registration.mapperConfig; + this.clientId = registration.clientId; + this.clientSecret = registration.clientSecret; + this.authorizationUri = registration.authorizationUri; + this.accessTokenUri = registration.accessTokenUri; + this.scope = registration.scope; + this.userInfoUri = registration.userInfoUri; + this.userNameAttributeName = registration.userNameAttributeName; + this.jwkSetUri = registration.jwkSetUri; + this.clientAuthenticationMethod = registration.clientAuthenticationMethod; + this.loginButtonLabel = registration.loginButtonLabel; + this.loginButtonIcon = registration.loginButtonIcon; + } + + @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + public String getName() { + return loginButtonLabel; + } + + @Override + public String getSearchText() { + return getName(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java similarity index 92% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java index 2598013ab6..449977f106 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ClientRegistrationDto.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2RegistrationInfo.java @@ -17,7 +17,6 @@ package org.thingsboard.server.common.data.oauth2; import com.fasterxml.jackson.databind.JsonNode; import lombok.*; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; import java.util.List; @@ -27,7 +26,7 @@ import java.util.List; @NoArgsConstructor @AllArgsConstructor @Builder -public class ClientRegistrationDto { +public class OAuth2RegistrationInfo { private OAuth2MapperConfig mapperConfig; private String clientId; private String clientSecret; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ClientRegistrationDto.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ClientRegistrationDto.java new file mode 100644 index 0000000000..4f491a2d8a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ClientRegistrationDto.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.oauth2.deprecated; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.*; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; + +import java.util.List; + +@Deprecated +@EqualsAndHashCode +@Data +@ToString(exclude = {"clientSecret"}) +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class ClientRegistrationDto { + private OAuth2MapperConfig mapperConfig; + private String clientId; + private String clientSecret; + private String authorizationUri; + private String accessTokenUri; + private List scope; + private String userInfoUri; + private String userNameAttributeName; + private String jwkSetUri; + private String clientAuthenticationMethod; + private String loginButtonLabel; + private String loginButtonIcon; + private JsonNode additionalInfo; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/DomainInfo.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/DomainInfo.java index fd6b93100c..5078c4483f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/DomainInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/DomainInfo.java @@ -13,10 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.oauth2.deprecated; import lombok.*; +import org.thingsboard.server.common.data.oauth2.SchemeType; +@Deprecated @EqualsAndHashCode @Data @ToString diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ExtendedOAuth2ClientRegistrationInfo.java similarity index 85% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ExtendedOAuth2ClientRegistrationInfo.java index 83647283a0..d071a6f1d4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/ExtendedOAuth2ClientRegistrationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/ExtendedOAuth2ClientRegistrationInfo.java @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.oauth2.deprecated; import lombok.Data; import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; +@Deprecated @EqualsAndHashCode(callSuper = true) @Data public class ExtendedOAuth2ClientRegistrationInfo extends OAuth2ClientRegistrationInfo { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistration.java similarity index 81% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistration.java index d0fa6648c8..cfac2de329 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistration.java @@ -13,16 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.oauth2.deprecated; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.BaseData; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationId; +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.oauth2.SchemeType; +@Deprecated @EqualsAndHashCode(callSuper = true) @Data @ToString diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistrationInfo.java similarity index 92% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistrationInfo.java index f70000693c..a99f56689b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientRegistrationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientRegistrationInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.oauth2.deprecated; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @@ -22,10 +22,12 @@ import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import java.util.List; +@Deprecated @EqualsAndHashCode(callSuper = true) @Data @ToString(exclude = {"clientSecret"}) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsDomainParams.java similarity index 92% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsDomainParams.java index 1570f71107..ff17fe2819 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsDomainParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsDomainParams.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.oauth2.deprecated; import lombok.*; import java.util.List; -import java.util.Set; +@Deprecated @EqualsAndHashCode @Data @ToString diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsParams.java similarity index 92% rename from common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java rename to common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsParams.java index 57c7351295..4f7735ef04 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/OAuth2ClientsParams.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/deprecated/OAuth2ClientsParams.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.oauth2; +package org.thingsboard.server.common.data.oauth2.deprecated; import lombok.*; import java.util.List; -import java.util.Set; +@Deprecated @EqualsAndHashCode @Data @ToString diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java b/common/data/src/main/java/org/thingsboard/server/common/data/ota/ChecksumAlgorithm.java similarity index 93% rename from common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ota/ChecksumAlgorithm.java index 3998482e35..ce33463054 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ota/ChecksumAlgorithm.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.firmware; +package org.thingsboard.server.common.data.ota; public enum ChecksumAlgorithm { MD5, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageKey.java similarity index 86% rename from common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKey.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageKey.java index cb38b6724c..8b9cdfa8fb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageKey.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.firmware; +package org.thingsboard.server.common.data.ota; import lombok.Getter; -public enum FirmwareKey { +public enum OtaPackageKey { - TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"); + TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url"); @Getter private final String value; - FirmwareKey(String value) { + OtaPackageKey(String value) { this.value = value; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareType.java b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageType.java similarity index 86% rename from common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageType.java index 5f6aa0d925..cab8cf1847 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageType.java @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.firmware; +package org.thingsboard.server.common.data.ota; import lombok.Getter; -public enum FirmwareType { +public enum OtaPackageType { FIRMWARE("fw"), SOFTWARE("sw"); @Getter private final String keyPrefix; - FirmwareType(String keyPrefix) { + OtaPackageType(String keyPrefix) { this.keyPrefix = keyPrefix; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUpdateStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageUpdateStatus.java similarity index 88% rename from common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUpdateStatus.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageUpdateStatus.java index 3e46174792..caa043c595 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUpdateStatus.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageUpdateStatus.java @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.firmware; +package org.thingsboard.server.common.data.ota; -public enum FirmwareUpdateStatus { +public enum OtaPackageUpdateStatus { QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageUtil.java similarity index 52% rename from common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUtil.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageUtil.java index 646ad24173..f2b29ec1f2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUtil.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageUtil.java @@ -13,21 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.firmware; +package org.thingsboard.server.common.data.ota; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.HasFirmware; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.HasOtaPackage; +import org.thingsboard.server.common.data.id.OtaPackageId; import java.util.ArrayList; import java.util.Collections; import java.util.List; - -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; -import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE; +import java.util.function.Supplier; @Slf4j -public class FirmwareUtil { +public class OtaPackageUtil { public static final List ALL_FW_ATTRIBUTE_KEYS; @@ -35,19 +33,19 @@ public class FirmwareUtil { static { ALL_FW_ATTRIBUTE_KEYS = new ArrayList<>(); - for (FirmwareKey key : FirmwareKey.values()) { - ALL_FW_ATTRIBUTE_KEYS.add(getAttributeKey(FIRMWARE, key)); + for (OtaPackageKey key : OtaPackageKey.values()) { + ALL_FW_ATTRIBUTE_KEYS.add(getAttributeKey(OtaPackageType.FIRMWARE, key)); } ALL_SW_ATTRIBUTE_KEYS = new ArrayList<>(); - for (FirmwareKey key : FirmwareKey.values()) { - ALL_SW_ATTRIBUTE_KEYS.add(getAttributeKey(SOFTWARE, key)); + for (OtaPackageKey key : OtaPackageKey.values()) { + ALL_SW_ATTRIBUTE_KEYS.add(getAttributeKey(OtaPackageType.SOFTWARE, key)); } } - public static List getAttributeKeys(FirmwareType firmwareType) { + public static List getAttributeKeys(OtaPackageType firmwareType) { switch (firmwareType) { case FIRMWARE: return ALL_FW_ATTRIBUTE_KEYS; @@ -57,35 +55,46 @@ public class FirmwareUtil { return Collections.emptyList(); } - public static String getAttributeKey(FirmwareType type, FirmwareKey key) { + public static String getAttributeKey(OtaPackageType type, OtaPackageKey key) { return type.getKeyPrefix() + "_" + key.getValue(); } - public static String getTargetTelemetryKey(FirmwareType type, FirmwareKey key) { + public static String getTargetTelemetryKey(OtaPackageType type, OtaPackageKey key) { return getTelemetryKey("target_", type, key); } - public static String getCurrentTelemetryKey(FirmwareType type, FirmwareKey key) { + public static String getCurrentTelemetryKey(OtaPackageType type, OtaPackageKey key) { return getTelemetryKey("current_", type, key); } - private static String getTelemetryKey(String prefix, FirmwareType type, FirmwareKey key) { + private static String getTelemetryKey(String prefix, OtaPackageType type, OtaPackageKey key) { return prefix + type.getKeyPrefix() + "_" + key.getValue(); } - public static String getTelemetryKey(FirmwareType type, FirmwareKey key) { + public static String getTelemetryKey(OtaPackageType type, OtaPackageKey key) { return type.getKeyPrefix() + "_" + key.getValue(); } - public static FirmwareId getFirmwareId(HasFirmware entity, FirmwareType firmwareType) { - switch (firmwareType) { + public static OtaPackageId getOtaPackageId(HasOtaPackage entity, OtaPackageType type) { + switch (type) { case FIRMWARE: return entity.getFirmwareId(); case SOFTWARE: return entity.getSoftwareId(); default: - log.warn("Unsupported firmware type: [{}]", firmwareType); + log.warn("Unsupported ota package type: [{}]", type); return null; } } + + public static T getByOtaPackageType(Supplier firmwareSupplier, Supplier softwareSupplier, OtaPackageType type) { + switch (type) { + case FIRMWARE: + return firmwareSupplier.get(); + case SOFTWARE: + return softwareSupplier.get(); + default: + throw new RuntimeException("Unsupported OtaPackage type: " + type); + } + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java index 2020245ea1..6ffbce4d3d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java @@ -17,10 +17,11 @@ package org.thingsboard.server.common.data.page; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import org.thingsboard.server.common.data.BaseData; import java.util.Collections; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; public class PageData { @@ -61,4 +62,8 @@ public class PageData { return hasNext; } + public PageData mapData(Function mapper) { + return new PageData<>(getData().stream().map(mapper).collect(Collectors.toList()), getTotalPages(), getTotalElements(), hasNext()); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index b9bd72b0db..8cdccfe8bd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -34,6 +34,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxUsers; private long maxDashboards; private long maxRuleChains; + private long maxResourcesInBytes; + private long maxOtaPackagesInBytes; private String transportTenantMsgRateLimit; private String transportTenantTelemetryMsgRateLimit; @@ -53,6 +55,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxCreatedAlarms; private int defaultStorageTtlDays; + private int alarmsTtlDays; private double warnThreshold; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java index 1a6a1a56e4..624918c8dd 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java @@ -38,7 +38,7 @@ public class TbKafkaTopicConfigs { private String notificationsProperties; @Value("${queue.kafka.topic-properties.js-executor}") private String jsExecutorProperties; - @Value("${queue.kafka.topic-properties.fw-updates:}") + @Value("${queue.kafka.topic-properties.ota-updates:}") private String fwUpdatesProperties; @Getter diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java index c2f4f4f750..d89b03c744 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java @@ -187,14 +187,14 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); } @PreDestroy diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java index 0267cdecb8..eaa73bc108 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -177,14 +177,14 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); } @PreDestroy 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 8806176f9f..cd1c34b612 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 @@ -131,13 +131,13 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new InMemoryTbQueueConsumer<>(coreSettings.getFirmwareTopic()); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new InMemoryTbQueueConsumer<>(coreSettings.getOtaPackageTopic()); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new InMemoryTbQueueProducer<>(coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new InMemoryTbQueueProducer<>(coreSettings.getOtaPackageTopic()); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index a526331116..c57260bef8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -277,24 +277,24 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); consumerBuilder.settings(kafkaSettings); - consumerBuilder.topic(coreSettings.getFirmwareTopic()); - consumerBuilder.clientId("monolith-fw-consumer-" + serviceInfoProvider.getServiceId()); - consumerBuilder.groupId("monolith-fw-consumer"); - consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.topic(coreSettings.getOtaPackageTopic()); + consumerBuilder.clientId("monolith-ota-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("monolith-ota-consumer"); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(fwUpdatesAdmin); consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); requestBuilder.settings(kafkaSettings); - requestBuilder.clientId("monolith-fw-producer-" + serviceInfoProvider.getServiceId()); - requestBuilder.defaultTopic(coreSettings.getFirmwareTopic()); + requestBuilder.clientId("monolith-ota-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getOtaPackageTopic()); requestBuilder.admin(fwUpdatesAdmin); return requestBuilder.build(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index 5f64e42ffd..bec01c7201 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -245,24 +245,24 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); consumerBuilder.settings(kafkaSettings); - consumerBuilder.topic(coreSettings.getFirmwareTopic()); - consumerBuilder.clientId("tb-core-fw-consumer-" + serviceInfoProvider.getServiceId()); - consumerBuilder.groupId("tb-core-fw-consumer"); - consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.topic(coreSettings.getOtaPackageTopic()); + consumerBuilder.clientId("tb-core-ota-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("tb-core-ota-consumer"); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.admin(fwUpdatesAdmin); consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); requestBuilder.settings(kafkaSettings); - requestBuilder.clientId("tb-core-fw-producer-" + serviceInfoProvider.getServiceId()); - requestBuilder.defaultTopic(coreSettings.getFirmwareTopic()); + requestBuilder.clientId("tb-core-ota-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getOtaPackageTopic()); requestBuilder.admin(fwUpdatesAdmin); return requestBuilder.build(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java index 3504c6aef2..3f014f578c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java @@ -22,9 +22,9 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; -import org.thingsboard.server.gen.transport.TransportProtos.*; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -192,14 +192,14 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getOtaPackageTopic()); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java index a9ebf5a4de..65ea183d66 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -166,14 +166,14 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getOtaPackageTopic()); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java index cd6043cdde..7730f8f5dd 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java @@ -24,7 +24,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -190,14 +190,14 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getOtaPackageTopic()); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java index 8b5ad00de4..c0abeab162 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -172,14 +172,14 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getOtaPackageTopic()); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java index b569d13742..2443093cf0 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -189,14 +189,14 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getOtaPackageTopic()); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java index 17e6eb9a27..7a9da236ed 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -172,14 +172,14 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { - return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getOtaPackageTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { - return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic()); + public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getOtaPackageTopic()); } @Override diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index be5ca967bf..584a6e389b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -16,7 +16,7 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -91,14 +91,14 @@ public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory { * * @return */ - TbQueueConsumer> createToFirmwareStateServiceMsgConsumer(); + TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer(); /** * Used to consume messages about firmware update notifications by TB Core Service * * @return */ - TbQueueProducer> createToFirmwareStateServiceMsgProducer(); + TbQueueProducer> createToOtaPackageStateServiceMsgProducer(); /** * Used to consume high priority messages by TB Core Service diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java index b7e7a155ed..6c8ab4455b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.queue.provider; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; @@ -25,11 +24,12 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.util.TbCoreComponent; import javax.annotation.PostConstruct; @Service -@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") +@TbCoreComponent public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { private final TbCoreQueueFactory tbQueueProvider; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java index d194fdb4ea..3c9ff5813e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java @@ -26,8 +26,8 @@ public class TbQueueCoreSettings { @Value("${queue.core.topic}") private String topic; - @Value("${queue.core.firmware.topic:tb_firmware}") - private String firmwareTopic; + @Value("${queue.core.ota.topic:tb_ota_package}") + private String otaPackageTopic; @Value("${queue.core.usage-stats-topic:tb_usage_stats}") private String usageStatsTopic; diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index b4d929f60c..acea8d6382 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -310,10 +310,12 @@ message SessionCloseNotificationProto { message SubscribeToAttributeUpdatesMsg { bool unsubscribe = 1; + SessionType sessionType = 2; } message SubscribeToRPCMsg { bool unsubscribe = 1; + SessionType sessionType = 2; } message ToDeviceRpcRequestMsg { @@ -388,7 +390,7 @@ enum ResponseStatus { FAILURE = 3; } -message GetFirmwareRequestMsg { +message GetOtaPackageRequestMsg { int64 deviceIdMSB = 1; int64 deviceIdLSB = 2; int64 tenantIdMSB = 3; @@ -396,10 +398,10 @@ message GetFirmwareRequestMsg { string type = 5; } -message GetFirmwareResponseMsg { +message GetOtaPackageResponseMsg { ResponseStatus responseStatus = 1; - int64 firmwareIdMSB = 2; - int64 firmwareIdLSB = 3; + int64 otaPackageIdMSB = 2; + int64 otaPackageIdLSB = 3; string type = 4; string title = 5; string version = 6; @@ -627,7 +629,7 @@ message TransportApiRequestMsg { ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; GetResourceRequestMsg resourceRequestMsg = 9; - GetFirmwareRequestMsg firmwareRequestMsg = 10; + GetOtaPackageRequestMsg otaPackageRequestMsg = 10; GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 11; GetDeviceRequestMsg deviceRequestMsg = 12; GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 13; @@ -642,7 +644,7 @@ message TransportApiResponseMsg { GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5; LwM2MResponseMsg lwM2MResponseMsg = 6; GetResourceResponseMsg resourceResponseMsg = 7; - GetFirmwareResponseMsg firmwareResponseMsg = 8; + GetOtaPackageResponseMsg otaPackageResponseMsg = 8; GetDeviceResponseMsg deviceResponseMsg = 9; GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 10; } @@ -710,13 +712,13 @@ message ToUsageStatsServiceMsg { int64 customerIdLSB = 7; } -message ToFirmwareStateServiceMsg { +message ToOtaPackageStateServiceMsg { int64 ts = 1; int64 tenantIdMSB = 2; int64 tenantIdLSB = 3; int64 deviceIdMSB = 4; int64 deviceIdLSB = 5; - int64 firmwareIdMSB = 6; - int64 firmwareIdLSB = 7; + int64 otaPackageIdMSB = 6; + int64 otaPackageIdLSB = 7; string type = 8; } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 47cf44f6fa..33f122fa3f 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -44,7 +44,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC 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.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.msg.session.SessionMsgType; @@ -350,10 +350,10 @@ public class CoapTransportResource extends AbstractCoapTransportResource { new CoapNoOpCallback(exchange)); break; case GET_FIRMWARE_REQUEST: - getFirmwareCallback(sessionInfo, exchange, FirmwareType.FIRMWARE); + getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.FIRMWARE); break; case GET_SOFTWARE_REQUEST: - getFirmwareCallback(sessionInfo, exchange, FirmwareType.SOFTWARE); + getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.SOFTWARE); break; } } catch (AdaptorException e) { @@ -366,14 +366,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); } - private void getFirmwareCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, FirmwareType firmwareType) { - TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) { + TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() .setTenantIdMSB(sessionInfo.getTenantIdMSB()) .setTenantIdLSB(sessionInfo.getTenantIdLSB()) .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) .setType(firmwareType.name()).build(); - transportContext.getTransportService().process(sessionInfo, requestMsg, new FirmwareCallback(exchange)); + transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange)); } private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { @@ -470,25 +470,28 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private class FirmwareCallback implements TransportServiceCallback { + private class OtaPackageCallback implements TransportServiceCallback { private final CoapExchange exchange; - FirmwareCallback(CoapExchange exchange) { + OtaPackageCallback(CoapExchange exchange) { this.exchange = exchange; } @Override - public void onSuccess(TransportProtos.GetFirmwareResponseMsg msg) { + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) { String title = exchange.getQueryParameter("title"); String version = exchange.getQueryParameter("version"); if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { + String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString(); if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) { - String firmwareId = new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB()).toString(); String strChunkSize = exchange.getQueryParameter("size"); String strChunk = exchange.getQueryParameter("chunk"); int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); - exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getFirmwareDataCache().get(firmwareId, chunkSize, chunk)); + exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk)); + } + else if (firmwareId != null) { + sendOtaData(exchange, firmwareId); } else { exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } @@ -504,6 +507,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } + private void sendOtaData(CoapExchange exchange, String firmwareId) { + Response response = new Response(CoAP.ResponseCode.CONTENT); + byte[] fwData = transportContext.getOtaPackageDataCache().get(firmwareId); + if (fwData != null && fwData.length > 0) { + response.setPayload(fwData); + if (exchange.getRequestOptions().getBlock2() != null) { + int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); + boolean moreFlag = fwData.length > chunkSize; + response.getOptions().setBlock2(chunkSize, moreFlag, 0); + } + exchange.respond(response); + } + } + private static class CoapSessionListener implements SessionMsgListener { private final CoapTransportResource coapTransportResource; diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index 31180b478d..f4a3f705af 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -34,7 +34,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.SessionMsgListener; @@ -213,7 +213,7 @@ public class DeviceApiController implements TbTransportService { @RequestParam(value = "version") String version, @RequestParam(value = "size", required = false, defaultValue = "0") int size, @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { - return getFirmwareCallback(deviceToken, title, version, size, chunk, FirmwareType.FIRMWARE); + return getOtaPackageCallback(deviceToken, title, version, size, chunk, OtaPackageType.FIRMWARE); } @RequestMapping(value = "/{deviceToken}/software", method = RequestMethod.GET) @@ -222,7 +222,7 @@ public class DeviceApiController implements TbTransportService { @RequestParam(value = "version") String version, @RequestParam(value = "size", required = false, defaultValue = "0") int size, @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { - return getFirmwareCallback(deviceToken, title, version, size, chunk, FirmwareType.SOFTWARE); + return getOtaPackageCallback(deviceToken, title, version, size, chunk, OtaPackageType.SOFTWARE); } @RequestMapping(value = "/provision", method = RequestMethod.POST) @@ -233,17 +233,17 @@ public class DeviceApiController implements TbTransportService { return responseWriter; } - private DeferredResult getFirmwareCallback(String deviceToken, String title, String version, int size, int chunk, FirmwareType firmwareType) { + private DeferredResult getOtaPackageCallback(String deviceToken, String title, String version, int size, int chunk, OtaPackageType firmwareType) { DeferredResult responseWriter = new DeferredResult<>(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { - TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() .setTenantIdMSB(sessionInfo.getTenantIdMSB()) .setTenantIdLSB(sessionInfo.getTenantIdLSB()) .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) .setType(firmwareType.name()).build(); - transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, title, version, size, chunk)); + transportContext.getTransportService().process(sessionInfo, requestMsg, new GetOtaPackageCallback(responseWriter, title, version, size, chunk)); })); return responseWriter; } @@ -294,14 +294,14 @@ public class DeviceApiController implements TbTransportService { } } - private class GetFirmwareCallback implements TransportServiceCallback { + private class GetOtaPackageCallback implements TransportServiceCallback { private final DeferredResult responseWriter; private final String title; private final String version; private final int chuckSize; private final int chuck; - GetFirmwareCallback(DeferredResult responseWriter, String title, String version, int chuckSize, int chuck) { + GetOtaPackageCallback(DeferredResult responseWriter, String title, String version, int chuckSize, int chuck) { this.responseWriter = responseWriter; this.title = title; this.version = version; @@ -310,17 +310,17 @@ public class DeviceApiController implements TbTransportService { } @Override - public void onSuccess(TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg) { - if (!TransportProtos.ResponseStatus.SUCCESS.equals(firmwareResponseMsg.getResponseStatus())) { + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg otaPackageResponseMsg) { + if (!TransportProtos.ResponseStatus.SUCCESS.equals(otaPackageResponseMsg.getResponseStatus())) { responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND)); - } else if (title.equals(firmwareResponseMsg.getTitle()) && version.equals(firmwareResponseMsg.getVersion())) { - String firmwareId = new UUID(firmwareResponseMsg.getFirmwareIdMSB(), firmwareResponseMsg.getFirmwareIdLSB()).toString(); - ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareDataCache().get(firmwareId, chuckSize, chuck)); + } else if (title.equals(otaPackageResponseMsg.getTitle()) && version.equals(otaPackageResponseMsg.getVersion())) { + String otaPackageId = new UUID(otaPackageResponseMsg.getOtaPackageIdMSB(), otaPackageResponseMsg.getOtaPackageIdLSB()).toString(); + ByteArrayResource resource = new ByteArrayResource(transportContext.getOtaPackageDataCache().get(otaPackageId, chuckSize, chuck)); ResponseEntity response = ResponseEntity.ok() - .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmwareResponseMsg.getFileName()) - .header("x-filename", firmwareResponseMsg.getFileName()) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackageResponseMsg.getFileName()) + .header("x-filename", otaPackageResponseMsg.getFileName()) .contentLength(resource.contentLength()) - .contentType(parseMediaType(firmwareResponseMsg.getContentType())) + .contentType(parseMediaType(otaPackageResponseMsg.getContentType())) .body(resource); responseWriter.setResult(response); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 9348cb31a5..8823881ae7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -102,15 +102,15 @@ public class LwM2MTransportBootstrapService { builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); /** Create CoAP Config */ - builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort())); + builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); /** Define model provider (Create Models )*/ /** Create credentials */ this.setServerWithCredentials(builder); - /** Set securityStore with new ConfigStore */ - builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); +// /** Set securityStore with new ConfigStore */ +// builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); /** SecurityStore */ builder.setSecurityStore(lwM2MBootstrapSecurityStore); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index 2f175a6bce..8ffc3ac1f2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -69,7 +69,7 @@ public class LwM2MBootstrapConfig { server0.lifetime = servers.getLifetime(); server0.defaultMinPeriod = servers.getDefaultMinPeriod(); server0.notifIfDisabled = servers.isNotifIfDisabled(); - server0.binding = BindingMode.valueOf(servers.getBinding()); + server0.binding = BindingMode.parse(servers.getBinding()); configBs.servers.put(0, server0); /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ this.bootstrapServer.setBootstrapServerIs(true); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index ccc2e62117..197c3ba842 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -30,7 +30,7 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; @@ -40,7 +40,7 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Collections; -import java.util.List; +import java.util.Iterator; import java.util.UUID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER; @@ -71,8 +71,8 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } @Override - public List getAllByEndpoint(String endPoint) { - EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); + public Iterator getAllByEndpoint(String endPoint) { + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); @@ -88,7 +88,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } catch (InvalidConfigurationException e) { log.error("", e); } - return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()); + return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator(); } } return null; @@ -96,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); @@ -113,7 +113,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { return null; } - private void setBootstrapConfigScurityInfo(EndpointSecurityInfo store) { + private void setBootstrapConfigScurityInfo(TbLwM2MSecurityInfo store) { /* BootstrapConfig */ LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); if (lwM2MBootstrapConfig != null) { @@ -150,7 +150,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } } - private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) { + private LwM2MBootstrapConfig getParametersBootstrap(TbLwM2MSecurityInfo store) { try { LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); if (lwM2MBootstrapConfig != null) { 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 bfe8b9ce4f..4e34c3dc6d 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 @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.server.bootstrap.BootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; @@ -25,7 +26,7 @@ import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; import java.util.Collections; -import java.util.List; +import java.util.Iterator; @Slf4j public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { @@ -50,16 +51,17 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession } @SuppressWarnings("deprecation") - public BootstrapSession begin(String endpoint, Identity clientIdentity) { + public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { boolean authorized; if (bsSecurityStore != null) { - List securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())) : bsSecurityStore.getAllByEndpoint(endpoint); + Iterator securityInfos = (clientIdentity.getPskIdentity() != null && !clientIdentity.getPskIdentity().isEmpty()) ? + Collections.singletonList(bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity())).iterator() : bsSecurityStore.getAllByEndpoint(request.getEndpointName()); log.info("Bootstrap session started securityInfos: [{}]", securityInfos); - authorized = securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos); + authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos); } else { authorized = true; } - DefaultBootstrapSession session = new DefaultBootstrapSession(endpoint, clientIdentity, authorized); + DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); log.info("Bootstrap session started : {}", session); return session; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 8d90b2a86b..13d6fd6568 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -22,16 +22,16 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -55,15 +55,15 @@ public class LwM2mCredentialsSecurityInfoValidator { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - public EndpointSecurityInfo getEndpointSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) { + public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTransportUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); - final EndpointSecurityInfo[] resultSecurityStore = new EndpointSecurityInfo[1]; - context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), + final TbLwM2MSecurityInfo[] resultSecurityStore = new TbLwM2MSecurityInfo[1]; + context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(credentialsId).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { String credentialsBody = msg.getCredentials(); - resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue); + resultSecurityStore[0] = createSecurityInfo(credentialsId, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile()); latch.countDown(); @@ -71,8 +71,8 @@ public class LwM2mCredentialsSecurityInfoValidator { @Override public void onError(Throwable e) { - log.trace("[{}] [{}] Failed to process credentials ", endpoint, e); - resultSecurityStore[0] = createSecurityInfo(endpoint, null, null); + log.trace("[{}] [{}] Failed to process credentials ", credentialsId, e); + resultSecurityStore[0] = createSecurityInfo(credentialsId, null, null); latch.countDown(); } }); @@ -91,8 +91,8 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return SecurityInfo */ - private EndpointSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { - EndpointSecurityInfo result = new EndpointSecurityInfo(); + private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { + TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo(); LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class); if (credentials != null) { if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { @@ -104,6 +104,7 @@ public class LwM2mCredentialsSecurityInfoValidator { result.setEndpoint(endpoint); result.setSecurityMode(credentials.getBootstrap().getBootstrapServer().getSecurityMode()); } else { + result.setEndpoint(credentials.getClient().getEndpoint()); switch (credentials.getClient().getSecurityConfigClientMode()) { case NO_SEC: createClientSecurityInfoNoSec(result); @@ -125,12 +126,12 @@ public class LwM2mCredentialsSecurityInfoValidator { return result; } - private void createClientSecurityInfoNoSec(EndpointSecurityInfo result) { + private void createClientSecurityInfoNoSec(TbLwM2MSecurityInfo result) { result.setSecurityInfo(null); result.setSecurityMode(NO_SEC); } - private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + private void createClientSecurityInfoPSK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig; if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { try { @@ -149,7 +150,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig; try { if (rpkConfig.getKey() != null) { @@ -164,7 +165,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { + private void createClientSecurityInfoX509(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityMode(X509); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java index 7269e78b5e..cd7f1f3072 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -27,6 +27,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; @Component @RequiredArgsConstructor @@ -34,7 +35,7 @@ import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore; public class TbLwM2MAuthorizer implements Authorizer { private final TbLwM2MDtlsSessionStore sessionStorage; - private final TbLwM2mSecurityStore securityStore; + private final TbSecurityStore securityStore; private final SecurityChecker securityChecker = new SecurityChecker(); private final LwM2mClientContext clientContext; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index 7b81e733bc..dcd7fa89bb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -29,22 +29,21 @@ import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier; import org.eclipse.californium.scandium.util.ServerNames; +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; +import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; import org.thingsboard.server.common.msg.EncryptionUtil; -import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.util.SslUtil; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; @@ -57,8 +56,6 @@ import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; @Slf4j @Component @@ -66,9 +63,10 @@ import java.util.concurrent.TimeUnit; @RequiredArgsConstructor public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { - private final TransportService transportService; private final TbLwM2MDtlsSessionStore sessionStorage; private final LwM2MTransportServerConfig config; + private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator; + private final TbEditableSecurityStore securityStore; @SuppressWarnings("deprecation") private StaticCertificateVerifier staticCertificateVerifier; @@ -119,48 +117,33 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer String strCert = SslUtil.getCertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1]; - CountDownLatch latch = new CountDownLatch(1); - transportService.process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(sha3Hash).build(), - new TransportServiceCallback<>() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (!StringUtils.isEmpty(msg.getCredentials())) { - deviceCredentialsResponse[0] = msg; - } - latch.countDown(); - } - - @Override - public void onError(Throwable e) { - log.error(e.getMessage(), e); - latch.countDown(); - } - }); - if (latch.await(10, TimeUnit.SECONDS)) { - ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; - if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { - LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); - if(!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)){ - continue; - } - X509ClientCredentials config = (X509ClientCredentials) credentials.getClient(); - String certBody = config.getCert(); - String endpoint = config.getEndpoint(); - if (strCert.equals(certBody)) { - x509CredentialsFound = true; - DeviceProfile deviceProfile = msg.getDeviceProfile(); - if (msg.hasDeviceInfo() && deviceProfile != null) { - sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); - break; + TbLwM2MSecurityInfo securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); + ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null; + if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { + LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); + if (!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)) { + continue; + } + X509ClientCredentials config = (X509ClientCredentials) credentials.getClient(); + String certBody = config.getCert(); + String endpoint = config.getEndpoint(); + if (strCert.equals(certBody)) { + x509CredentialsFound = true; + DeviceProfile deviceProfile = msg.getDeviceProfile(); + if (msg.hasDeviceInfo() && deviceProfile != null) { + sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); + try { + securityStore.put(securityInfo); + } catch (NonUniqueSecurityInfoException e) { + log.trace("Failed to add security info: {}", securityInfo, e); } - } else { - log.trace("[{}][{}] Certificate mismatch. Expected: {}, Actual: {}", endpoint, sha3Hash, strCert, certBody); + break; } + } else { + log.trace("[{}][{}] Certificate mismatch. Expected: {}, Actual: {}", endpoint, sha3Hash, strCert, certBody); } } - } catch (InterruptedException | - CertificateEncodingException | + } catch (CertificateEncodingException | CertificateExpiredException | CertificateNotYetValidException e) { log.error(e.getMessage(), e); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java similarity index 97% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java index e8d3ae3c2b..9b9147c44f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @Data -public class EndpointSecurityInfo { +public class TbLwM2MSecurityInfo { private ValidateDeviceCredentialsResponse msg; private SecurityInfo securityInfo; private SecurityMode securityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java new file mode 100644 index 0000000000..2c82facd23 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/AbstractLwM2mTransportResource.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.leshan.core.californium.LwM2mCoapResource; +import org.thingsboard.server.common.transport.TransportServiceCallback; + +@Slf4j +public abstract class AbstractLwM2mTransportResource extends LwM2mCoapResource { + + public AbstractLwM2mTransportResource(String name) { + super(name); + } + + @Override + public void handleGET(CoapExchange exchange) { + processHandleGet(exchange); + } + + @Override + public void handlePOST(CoapExchange exchange) { + processHandlePost(exchange); + } + + protected abstract void processHandleGet(CoapExchange exchange); + + protected abstract void processHandlePost(CoapExchange exchange); + + public static class CoapOkCallback implements TransportServiceCallback { + private final CoapExchange exchange; + private final CoAP.ResponseCode onSuccessResponse; + private final CoAP.ResponseCode onFailureResponse; + + public CoapOkCallback(CoapExchange exchange, CoAP.ResponseCode onSuccessResponse, CoAP.ResponseCode onFailureResponse) { + this.exchange = exchange; + this.onSuccessResponse = onSuccessResponse; + this.onFailureResponse = onFailureResponse; + } + + @Override + public void onSuccess(Void msg) { + Response response = new Response(onSuccessResponse); + response.setAcknowledged(isConRequest()); + exchange.respond(response); + } + + @Override + public void onError(Throwable e) { + exchange.respond(onFailureResponse); + } + + private boolean isConRequest() { + return exchange.advanced().getRequest().isConfirmable(); + } + } + + public static class CoapNoOpCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + CoapNoOpCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(Void msg) { + } + + @Override + public void onError(Throwable e) { + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + } + } + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index 9092f8abeb..bff3b9d60d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -31,7 +31,6 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; @@ -39,13 +38,13 @@ import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.cache.firmware.FirmwareDataCache; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.firmware.FirmwareKey; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.firmware.FirmwareUtil; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.common.data.ota.OtaPackageKey; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; @@ -58,10 +57,13 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientStateException; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; -import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcRequest; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mFwSwUpdate; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; @@ -87,21 +89,20 @@ import java.util.stream.Collectors; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADED; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATING; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEVICE_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_STRATEGY_2; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_WARN; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL_ALL; @@ -109,8 +110,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getAckCallback; @@ -132,28 +133,30 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler private final TransportService transportService; private final LwM2mTransportContext context; public final LwM2MTransportServerConfig config; - public final FirmwareDataCache firmwareDataCache; + public final OtaPackageDataCache otaPackageDataCache; public final LwM2mTransportServerHelper helper; private final LwM2MJsonAdaptor adaptor; private final TbLwM2MDtlsSessionStore sessionStore; public final LwM2mClientContext clientContext; public final LwM2mTransportRequest lwM2mTransportRequest; private final Map rpcSubscriptions; + public final Map firmwareUpdateState; public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, LwM2mClientContext clientContext, @Lazy LwM2mTransportRequest lwM2mTransportRequest, - FirmwareDataCache firmwareDataCache, + OtaPackageDataCache otaPackageDataCache, LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) { this.transportService = transportService; this.config = config; this.helper = helper; this.clientContext = clientContext; this.lwM2mTransportRequest = lwM2mTransportRequest; - this.firmwareDataCache = firmwareDataCache; + this.otaPackageDataCache = otaPackageDataCache; this.context = context; this.adaptor = adaptor; this.rpcSubscriptions = new ConcurrentHashMap<>(); + this.firmwareUpdateState = new ConcurrentHashMap<>(); this.sessionStore = sessionStore; } @@ -182,32 +185,39 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ public void onRegistered(Registration registration, Collection previousObservations) { registrationExecutor.submit(() -> { + LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); try { log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); - LwM2mClient lwM2MClient = this.clientContext.registerOrUpdate(registration); if (lwM2MClient != null) { - SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); - if (sessionInfo != null) { - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); - TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder() - .setSessionInfo(sessionInfo) - .setSessionEvent(DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN)) - .setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build()) - .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().build()) - .build(); - transportService.process(msg, null); - this.getInfoFirmwareUpdate(lwM2MClient); - this.getInfoSoftwareUpdate(lwM2MClient); - this.initLwM2mFromClientValue(registration, lwM2MClient); - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration.getId()); - } else { - log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); - } + this.clientContext.register(lwM2MClient, registration); + this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_INFO + ": Client registered with registration id: " + registration.getId()); + SessionInfoProto sessionInfo = lwM2MClient.getSession(); + transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); + log.warn("40) sessionId [{}] Registering rpc subscription after Registration client", new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); + TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder() + .setSessionInfo(sessionInfo) + .setSessionEvent(DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN)) + .setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setSessionType(TransportProtos.SessionType.ASYNC).build()) + .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().setSessionType(TransportProtos.SessionType.ASYNC).build()) + .build(); + transportService.process(msg, null); + this.getInfoFirmwareUpdate(lwM2MClient, null); + this.getInfoSoftwareUpdate(lwM2MClient, null); + this.initClientTelemetry(lwM2MClient); } else { - log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), null); + log.error("Client: [{}] onRegistered [{}] name [{}] lwM2MClient ", registration.getId(), registration.getEndpoint(), null); + } + } catch (LwM2MClientStateException stateException) { + if (LwM2MClientState.UNREGISTERED.equals(stateException.getState())) { + log.info("[{}] retry registration due to race condition: [{}].", registration.getEndpoint(), stateException.getState()); + // Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again. + onRegistered(registration, previousObservations); + } else { + this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); } } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t); + this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_WARN + ": Client registration failed due to: " + t.getMessage()); } }); } @@ -219,25 +229,26 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ public void updatedReg(Registration registration) { updateRegistrationExecutor.submit(() -> { + LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); try { - LwM2mClient client = clientContext.getOrRegister(registration); - if (client != null && client.getSession() != null) { - SessionInfoProto sessionInfo = client.getSession(); - this.reportActivityAndRegister(sessionInfo); - if (registration.getBindingMode().useQueueMode()) { - LwM2mQueuedRequest request; - while ((request = client.getQueuedRequests().poll()) != null) { - request.send(); - } + clientContext.updateRegistration(lwM2MClient, registration); + TransportProtos.SessionInfoProto sessionInfo = lwM2MClient.getSession(); + this.reportActivityAndRegister(sessionInfo); + if (registration.usesQueueMode()) { + LwM2mQueuedRequest request; + while ((request = lwM2MClient.getQueuedRequests().poll()) != null) { + request.send(); } - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client update Registration", registration.getId()); + } + } catch (LwM2MClientStateException stateException) { + if (LwM2MClientState.REGISTERED.equals(stateException.getState())) { + log.info("[{}] update registration failed because client has different registration id: [{}] {}.", registration.getEndpoint(), stateException.getState(), stateException.getMessage()); } else { - log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); - this.sendLogsToThingsboard(LOG_LW2M_ERROR + ": Client update Registration", registration.getId()); + onRegistered(registration, Collections.emptyList()); } } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); - this.sendLogsToThingsboard(LOG_LW2M_ERROR + String.format(": Client update Registration, %s", t.getMessage()), registration.getId()); + this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_ERROR + String.format(": Client update Registration, %s", t.getMessage())); } }); } @@ -247,36 +258,33 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param observations - !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect */ public void unReg(Registration registration, Collection observations) { - log.error("Client unRegistration -> test", new RuntimeException()); unRegistrationExecutor.submit(() -> { + LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); try { - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId()); - this.closeClientSession(registration); + this.sendLogsToThingsboard(client, LOG_LW2M_INFO + ": Client unRegistration"); + clientContext.unregister(client, registration); + SessionInfoProto sessionInfo = client.getSession(); + if (sessionInfo != null) { + this.doCloseSession(sessionInfo); + transportService.deregisterSession(sessionInfo); + sessionStore.remove(registration.getEndpoint()); + log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); + } else { + log.error("Client close session: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); + } + } catch (LwM2MClientStateException stateException) { + log.info("[{}] delete registration: [{}] {}.", registration.getEndpoint(), stateException.getState(), stateException.getMessage()); } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); - this.sendLogsToThingsboard(LOG_LW2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage()), registration.getId()); + this.sendLogsToThingsboard(client, LOG_LW2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage())); } }); } - private void closeClientSession(Registration registration) { - SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); - if (sessionInfo != null) { - transportService.deregisterSession(sessionInfo); - sessionStore.remove(registration.getEndpoint()); - this.doCloseSession(sessionInfo); - clientContext.removeClientByRegistrationId(registration.getId()); - log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); - } else { - log.error("Client close session: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); - } - } - @Override public void onSleepingDev(Registration registration) { log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is sleeping!", registration.getId()); - + this.sendLogsToThingsboard(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LW2M_INFO + ": Client is sleeping!"); //TODO: associate endpointId with device information. } @@ -286,8 +294,11 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @Override public void setCancelObservationsAll(Registration registration) { if (registration != null) { - lwM2mTransportRequest.sendAllRequest(registration, null, OBSERVE_CANCEL_ALL, - null, null, this.config.getTimeout(), null); + LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); + if (client != null && client.getRegistration() != null && client.getRegistration().getId().equals(registration.getId())) { + this.lwM2mTransportRequest.sendAllRequest(client, null, OBSERVE_CANCEL_ALL, + null, null, this.config.getTimeout(), null); + } } } @@ -299,9 +310,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param response - observe */ @Override - public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) { + public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, LwM2mClientRpcRequest rpcRequest) { if (response.getContent() != null) { - LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); + LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path, this.config.getModelProvider()); if (objectModelVersion != null) { if (response.getContent() instanceof LwM2mObject) { @@ -322,7 +333,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } private void sendRpcRequestAfterReadResponse(Registration registration, LwM2mClient lwM2MClient, String pathIdVer, ReadResponse response, - Lwm2mClientRpcRequest rpcRequest) { + LwM2mClientRpcRequest rpcRequest) { Object value = null; if (response.getContent() instanceof LwM2mObject) { value = lwM2MClient.objectToString((LwM2mObject) response.getContent(), this.converter, pathIdVer); @@ -333,9 +344,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } String msg = String.format("%s: type operation %s path - %s value - %s", LOG_LW2M_INFO, READ, pathIdVer, value); - this.sendLogsToThingsboard(msg, registration.getId()); + this.sendLogsToThingsboard(lwM2MClient, msg); rpcRequest.setValueMsg(String.format("%s", value)); - this.sentRpcRequest(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); + this.sentRpcResponse(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); } /** @@ -351,22 +362,23 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { - LwM2mClient lwM2MClient = clientContext.getClient(sessionInfo); - if (msg.getSharedUpdatedCount() > 0) { + LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo); + if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) { + log.warn("2) OnAttributeUpdate, SharedUpdatedList() [{}]", msg.getSharedUpdatedList()); msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); Object valueNew = getValueFromKvProto(tsKvProto.getKv()); - if ((FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) + if ((OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion()))) - || (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.TITLE).equals(pathName) + || (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName) && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentTitle())))) { - this.getInfoFirmwareUpdate(lwM2MClient); - } else if ((FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.VERSION).equals(pathName) + this.getInfoFirmwareUpdate(lwM2MClient, null); + } else if ((OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName) && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentVersion()))) - || (FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.TITLE).equals(pathName) + || (OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName) && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentTitle())))) { - this.getInfoSoftwareUpdate(lwM2MClient); + this.getInfoSoftwareUpdate(lwM2MClient, null); } if (pathIdVer != null) { ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config @@ -377,25 +389,27 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, valueNew); String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", LOG_LW2M_ERROR, pathIdVer, valueNew); - this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId()); + this.sendLogsToThingsboard(lwM2MClient, logMsg); } } else if (!isFwSwWords(pathName)) { log.error("Resource name name - [{}] value - [{}] is not present as attribute/telemetry in profile and cannot be updated", pathName, valueNew); String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", LOG_LW2M_ERROR, pathName, valueNew); - this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId()); + this.sendLogsToThingsboard(lwM2MClient, logMsg); } }); - } else if (msg.getSharedDeletedCount() > 0) { + } else if (msg.getSharedDeletedCount() > 0 && lwM2MClient != null) { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); Object valueNew = getValueFromKvProto(tsKvProto.getKv()); - if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) { + if (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) { lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew); } }); log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); + } else if (lwM2MClient == null) { + log.error("OnAttributeUpdate, lwM2MClient is null"); } } @@ -405,12 +419,11 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ @Override public void onDeviceProfileUpdate(SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { - Set clients = clientContext.getLwM2mClients() - .stream().filter(e -> e.getProfileId().equals(deviceProfile.getUuidId())).collect(Collectors.toSet()); + List clients = clientContext.getLwM2mClients() + .stream().filter(e -> e.getProfileId().equals(deviceProfile.getUuidId())).collect(Collectors.toList()); clients.forEach(client -> client.onDeviceProfileUpdate(deviceProfile)); - Set registrationIds = clients.stream().map(LwM2mClient::getRegistration).map(Registration::getId).collect(Collectors.toSet()); - if (registrationIds.size() > 0) { - this.onDeviceProfileUpdate(registrationIds, deviceProfile); + if (clients.size() > 0) { + this.onDeviceProfileUpdate(clients, deviceProfile); } } @@ -437,49 +450,65 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler /** * #1 del from rpcSubscriptions by timeout - * #2 if not present in rpcSubscriptions by requestId: create new Lwm2mClientRpcRequest, after success - add requestId, timeout + * #2 if not present in rpcSubscriptions by requestId: create new LwM2mClientRpcRequest, after success - add requestId, timeout */ @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg, SessionInfoProto sessionInfo) { // #1 this.checkRpcRequestTimeout(); + log.warn("4) toDeviceRpcRequestMsg: [{}], sessionUUID: [{}]", toDeviceRpcRequestMsg, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); String bodyParams = StringUtils.trimToNull(toDeviceRpcRequestMsg.getParams()) != null ? toDeviceRpcRequestMsg.getParams() : "null"; LwM2mTypeOper lwM2mTypeOper = setValidTypeOper(toDeviceRpcRequestMsg.getMethodName()); UUID requestUUID = new UUID(toDeviceRpcRequestMsg.getRequestIdMSB(), toDeviceRpcRequestMsg.getRequestIdLSB()); if (!this.rpcSubscriptions.containsKey(requestUUID)) { this.rpcSubscriptions.put(requestUUID, toDeviceRpcRequestMsg.getExpirationTime()); - Lwm2mClientRpcRequest lwm2mClientRpcRequest = null; + LwM2mClientRpcRequest lwm2mClientRpcRequest = null; try { - Registration registration = clientContext.getClient(sessionInfo).getRegistration(); - lwm2mClientRpcRequest = new Lwm2mClientRpcRequest(lwM2mTypeOper, bodyParams, toDeviceRpcRequestMsg.getRequestId(), sessionInfo, registration, this); - if (lwm2mClientRpcRequest.getErrorMsg() != null) { - lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); - this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); + LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); + Registration registration = client.getRegistration(); + if(registration != null) { + lwm2mClientRpcRequest = new LwM2mClientRpcRequest(lwM2mTypeOper, bodyParams, toDeviceRpcRequestMsg.getRequestId(), sessionInfo, registration, this); + if (lwm2mClientRpcRequest.getErrorMsg() != null) { + lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); + this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); + } else { + lwM2mTransportRequest.sendAllRequest(client, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), + null, + lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), + this.config.getTimeout(), lwm2mClientRpcRequest); + } } else { - lwM2mTransportRequest.sendAllRequest(registration, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), - null, - lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), - this.config.getTimeout(), lwm2mClientRpcRequest); + this.sendErrorRpcResponse(lwm2mClientRpcRequest, "registration == null", sessionInfo); } } catch (Exception e) { - if (lwm2mClientRpcRequest == null) { - lwm2mClientRpcRequest = new Lwm2mClientRpcRequest(); - } - lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); - if (lwm2mClientRpcRequest.getErrorMsg() == null) { - lwm2mClientRpcRequest.setErrorMsg(e.getMessage()); - } - this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); + this.sendErrorRpcResponse(lwm2mClientRpcRequest, e.getMessage(), sessionInfo); } } } + private void sendErrorRpcResponse(LwM2mClientRpcRequest lwm2mClientRpcRequest, String msgError, SessionInfoProto sessionInfo) { + if (lwm2mClientRpcRequest == null) { + lwm2mClientRpcRequest = new LwM2mClientRpcRequest(); + } + lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); + if (lwm2mClientRpcRequest.getErrorMsg() == null) { + lwm2mClientRpcRequest.setErrorMsg(msgError); + } + this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); + } + private void checkRpcRequestTimeout() { - Set rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> System.currentTimeMillis() > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); - rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); + log.warn("4.1) before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); + if (rpcSubscriptions.size() > 0) { + Set rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> System.currentTimeMillis() > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); + log.warn("4.2) System.currentTimeMillis(): [{}]", System.currentTimeMillis()); + log.warn("4.3) rpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); + rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); + } + log.warn("4.4) after rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); } - public void sentRpcRequest(Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { + public void sentRpcResponse(LwM2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { rpcRequest.setResponseCode(requestCode); if (LOG_LW2M_ERROR.equals(typeMsg)) { rpcRequest.setInfoMsg(null); @@ -502,6 +531,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @Override public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, SessionInfoProto sessionInfo) { + log.warn("5) onToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); transportService.process(sessionInfo, toDeviceResponse, null); } @@ -509,17 +539,6 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler log.info("[{}] toServerRpcResponse", toServerResponse); } - /** - * Trigger Server path = "/1/0/8" - *

- * Trigger bootStrap path = "/1/0/9" - have to implemented on client - */ - @Override - public void doTrigger(Registration registration, String path) { - lwM2mTransportRequest.sendAllRequest(registration, path, EXECUTE, - ContentFormat.TLV.getName(), null, this.config.getTimeout(), null); - } - /** * Deregister session in transport * @@ -553,7 +572,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @Override public void onAwakeDev(Registration registration) { log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId()); + this.sendLogsToThingsboard(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LW2M_INFO + ": Client is awake!"); //TODO: associate endpointId with device information. } @@ -562,13 +581,17 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param registrationId - Id of Registration LwM2M Client */ @Override - public void sendLogsToThingsboard(String logMsg, String registrationId) { - SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registrationId); - if (logMsg != null && sessionInfo != null) { + public void sendLogsToThingsboard(String registrationId, String logMsg) { + sendLogsToThingsboard(clientContext.getClientByRegistrationId(registrationId), logMsg); + } + + @Override + public void sendLogsToThingsboard(LwM2mClient client, String logMsg) { + if (logMsg != null && client != null && client.getSession() != null) { if (logMsg.length() > 1024) { logMsg = logMsg.substring(0, 1024); } - this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo); + this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), client.getSession()); } } @@ -581,24 +604,23 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * - Read Request to the client after registration to read all resource values for all objects * - then Observe Request to the client marked as observe from the profile configuration. * - * @param registration - Registration LwM2M Client - * @param lwM2MClient - object with All parameters off client + * @param lwM2MClient - object with All parameters off client */ - private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) { - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(registration); - Set clientObjects = clientContext.getSupportedIdVerInClient(registration); + private void initClientTelemetry(LwM2mClient lwM2MClient) { + LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(lwM2MClient.getProfileId()); + Set clientObjects = clientContext.getSupportedIdVerInClient(lwM2MClient); if (clientObjects != null && clientObjects.size() > 0) { - if (LWM2M_STRATEGY_2 == LwM2mTransportUtil.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { + if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == lwM2MClientProfile.getClientStrategy()) { // #2 lwM2MClient.getPendingReadRequests().addAll(clientObjects); - clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(), + clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, READ, null, this.config.getTimeout(), null)); } // #1 - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, READ, clientObjects); - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, OBSERVE, clientObjects); - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, WRITE_ATTRIBUTES, clientObjects); - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, DISCOVER, clientObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, READ, clientObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, OBSERVE, clientObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, WRITE_ATTRIBUTES, clientObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, DISCOVER, clientObjects); } } @@ -640,65 +662,23 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param path - resource */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { - LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); + LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { /** version != null * set setClient_fw_info... = value **/ - if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { + if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { lwM2MClient.getFwUpdate().initReadValue(this, this.lwM2mTransportRequest, path); } - if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { + if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { lwM2MClient.getSwUpdate().initReadValue(this, this.lwM2mTransportRequest, path); } - /** - * Before operation Execute (FwUpdate) inspection Update Result : - * - after finished operation Write result: success (FwUpdate): fw_state = DOWNLOADED - * - before start operation Execute (FwUpdate) Update Result = 0 - Initial value - * - start Execute (FwUpdate) - * After finished operation Execute (FwUpdate) inspection Update Result : - * - after start operation Execute (FwUpdate): fw_state = UPDATING - * - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully") - * - finished operation Execute (FwUpdate) - */ - if (lwM2MClient.getFwUpdate() != null - && (convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { - if (DOWNLOADED.name().equals(lwM2MClient.getFwUpdate().getStateUpdate()) - && lwM2MClient.getFwUpdate().conditionalFwExecuteStart()) { - lwM2MClient.getFwUpdate().executeFwSwWare(this, this.lwM2mTransportRequest); - } else if (UPDATING.name().equals(lwM2MClient.getFwUpdate().getStateUpdate()) - && lwM2MClient.getFwUpdate().conditionalFwExecuteAfterSuccess()) { - lwM2MClient.getFwUpdate().finishFwSwUpdate(this, true); - } else if (UPDATING.name().equals(lwM2MClient.getFwUpdate().getStateUpdate()) - && lwM2MClient.getFwUpdate().conditionalFwExecuteAfterError()) { - lwM2MClient.getFwUpdate().finishFwSwUpdate(this, false); - } - } - - /** - * Before operation Execute (SwUpdate) inspection Update Result : - * - after finished operation Write result: success (SwUpdate): fw_state = DOWNLOADED - * - before operation Execute (SwUpdate) Update Result = 3 - Successfully Downloaded and package integrity verified - * - start Execute (SwUpdate) - * After finished operation Execute (SwUpdate) inspection Update Result : - * - after start operation Execute (SwUpdate): fw_state = UPDATING - * - after success finished operation Execute (SwUpdate) Update Result == 2 "Software successfully installed."" - * - after success finished operation Execute (SwUpdate) Update Result == 2 "Software successfully installed."" - * - finished operation Execute (SwUpdate) - */ - if (lwM2MClient.getSwUpdate() != null - && (convertPathFromObjectIdToIdVer(SW_RESULT_ID, registration).equals(path))) { - if (DOWNLOADED.name().equals(lwM2MClient.getSwUpdate().getStateUpdate()) - && lwM2MClient.getSwUpdate().conditionalSwUpdateExecute()) { - lwM2MClient.getSwUpdate().executeFwSwWare(this, this.lwM2mTransportRequest); - } else if (UPDATING.name().equals(lwM2MClient.getSwUpdate().getStateUpdate()) - && lwM2MClient.getSwUpdate().conditionalSwExecuteAfterSuccess()) { - lwM2MClient.getSwUpdate().finishFwSwUpdate(this, true); - } else if (UPDATING.name().equals(lwM2MClient.getSwUpdate().getStateUpdate()) - && lwM2MClient.getSwUpdate().conditionalSwExecuteAfterError()) { - lwM2MClient.getSwUpdate().finishFwSwUpdate(this, false); - } + if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path)) || + (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) { + LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(clientContext); + log.warn("93) path: [{}] value: [{}]", path, lwM2mResource.getValue()); + fwUpdate.updateStateOta(this, lwM2mTransportRequest, registration, path, ((Long) lwM2mResource.getValue()).intValue()); } Set paths = new HashSet<>(); paths.add(path); @@ -735,15 +715,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } } - /** - * Start observe/read: Attr/Telemetry - * #1 - Analyze: path in resource profile == client resource - * - * @param registration - - */ - private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, - LwM2mTypeOper typeOper, Set clientObjects) { - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(registration); + private void initReadAttrTelemetryObserveToClient(LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, Set clientObjects) { + LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(lwM2MClient.getProfileId()); Set result = null; ConcurrentHashMap params = null; if (READ.equals(typeOper)) { @@ -763,28 +736,42 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()); result = params.keySet(); } - if (result != null && !result.isEmpty()) { - // #1 - Set pathSend = result.stream().filter(target -> { - return target.split(LWM2M_SEPARATOR_PATH).length < 3 ? - clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]) : - clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1] + "/" + target.split(LWM2M_SEPARATOR_PATH)[2]); - } + sendRequestsToClient(lwM2MClient, typeOper, clientObjects, result, params); + } + + private void sendRequestsToClient(LwM2mClient lwM2MClient, LwM2mTypeOper operationType, Set supportedObjectIds, Set desiredObjectIds, ConcurrentHashMap params) { + if (desiredObjectIds != null && !desiredObjectIds.isEmpty()) { + Set targetObjectIds = desiredObjectIds.stream().filter(target -> isSupportedTargetId(supportedObjectIds, target) ).collect(Collectors.toUnmodifiableSet()); - if (!pathSend.isEmpty()) { - lwM2MClient.getPendingReadRequests().addAll(pathSend); - ConcurrentHashMap finalParams = params; - pathSend.forEach(target -> { - lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - finalParams != null ? finalParams.get(target) : null, this.config.getTimeout(), null); + if (!targetObjectIds.isEmpty()) { + //TODO: remove this side effect? + lwM2MClient.getPendingReadRequests().addAll(targetObjectIds); + targetObjectIds.forEach(target -> { + Object additionalParams = params != null ? params.get(target) : null; + lwM2mTransportRequest.sendAllRequest(lwM2MClient, target, operationType, additionalParams, this.config.getTimeout(), null); }); - if (OBSERVE.equals(typeOper)) { + if (OBSERVE.equals(operationType)) { lwM2MClient.initReadValue(this, null); } } } } + private boolean isSupportedTargetId(Set supportedIds, String targetId) { + String[] targetIdParts = targetId.split(LWM2M_SEPARATOR_PATH); + if (targetIdParts.length <= 1) { + return false; + } + String targetIdSearch = targetIdParts[0]; + for (int i = 1; i < targetIdParts.length; i++) { + targetIdSearch += "/" + targetIdParts[i]; + if (supportedIds.contains(targetIdSearch)) { + return true; + } + } + return false; + } + private ConcurrentHashMap getPathForWriteAttributes(JsonObject objectJson) { ConcurrentHashMap pathAttributes = new Gson().fromJson(objectJson.toString(), new TypeToken>() { @@ -793,7 +780,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } private void onDeviceUpdate(LwM2mClient lwM2MClient, Device device, Optional deviceProfileOpt) { - deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singleton(lwM2MClient.getRegistration().getId()), deviceProfile)); + deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singletonList(lwM2MClient), deviceProfile)); lwM2MClient.onDeviceUpdate(device, deviceProfileOpt); } @@ -838,7 +825,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } private TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { - LwM2mClient lwM2MClient = this.clientContext.getClientByRegistrationId(registration.getId()); + LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); JsonObject names = clientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); if (names != null && names.has(pathIdVer)) { String resourceName = names.get(pathIdVer).getAsString(); @@ -853,8 +840,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler valueKvProto = new JsonObject(); Object finalvalueKvProto = valueKvProto; Gson gson = new GsonBuilder().create(); - resourceValue.getValues().forEach((k, v) -> { - Object val = this.converter.convertValue(v, currentType, expectedType, + ResourceModel.Type finalCurrentType = currentType; + resourceValue.getInstances().forEach((k, v) -> { + Object val = this.converter.convertValue(v, finalCurrentType, expectedType, new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); JsonElement element = gson.toJsonTree(val, val.getClass()); ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); @@ -864,6 +852,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); } + LwM2mOtaConvert lwM2mOtaConvert = convertOtaUpdateValueToString(pathIdVer, valueKvProto, currentType); + valueKvProto = lwM2mOtaConvert.getValue(); + currentType = lwM2mOtaConvert.getCurrentType(); return valueKvProto != null ? this.helper.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null; } } catch (Exception e) { @@ -882,10 +873,14 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); - ResourceModel.Type currentType = resourceValue.getType(); - ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); - return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, - new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); + if (resourceValue != null) { + ResourceModel.Type currentType = resourceValue.getType(); + ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); + return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, + new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); + } else { + return null; + } } /** @@ -944,10 +939,10 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * #6.1 - update WriteAttribute * #6.2 - del WriteAttribute * - * @param registrationIds - - * @param deviceProfile - + * @param clients - + * @param deviceProfile - */ - private void onDeviceProfileUpdate(Set registrationIds, DeviceProfile deviceProfile) { + private void onDeviceProfileUpdate(List clients, DeviceProfile deviceProfile) { LwM2mClientProfile lwM2MClientProfileOld = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); if (clientContext.profileUpdate(deviceProfile) != null) { // #1 @@ -998,15 +993,14 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler // #3.4, #6 if (!attributeLwm2mOld.equals(attributeLwm2mNew)) { - this.getAnalyzerAttributeLwm2m(registrationIds, attributeLwm2mOld, attributeLwm2mNew); + this.getAnalyzerAttributeLwm2m(clients, attributeLwm2mOld, attributeLwm2mNew); } // #4.1 add if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) { // update value in Resources - registrationIds.forEach(registrationId -> { - Registration registration = clientContext.getRegistration(registrationId); - this.readObserveFromProfile(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ); + clients.forEach(client -> { + this.readObserveFromProfile(client, sendAttrToThingsboard.getPathPostParametersAdd(), READ); }); } // #4.2 del @@ -1030,15 +1024,15 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler // does not include oldObserve ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sendObserveToClientOld.getPathPostParametersAdd(), sendObserveToClientNew.getPathPostParametersAdd()); // send Request observe to Client - registrationIds.forEach(registrationId -> { - Registration registration = clientContext.getRegistration(registrationId); + clients.forEach(client -> { + Registration registration = client.getRegistration(); if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) { - this.readObserveFromProfile(registration, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); + this.readObserveFromProfile(client, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); } // 5.3 del // send Request cancel observe to Client if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) { - this.cancelObserveFromProfile(registration, postObserveAnalyzer.getPathPostParametersDel()); + this.cancelObserveFromProfile(client, postObserveAnalyzer.getPathPostParametersDel()); } }); } @@ -1075,19 +1069,18 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * Update Resource value after change RezAttrTelemetry in config Profile * send response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue() * - * @param registration - Registration LwM2M Client - * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] + * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] */ - private void readObserveFromProfile(Registration registration, Set targets, LwM2mTypeOper typeOper) { + private void readObserveFromProfile(LwM2mClient client, Set targets, LwM2mTypeOper typeOper) { targets.forEach(target -> { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target)); if (pathIds.isResource()) { if (READ.equals(typeOper)) { - lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - ContentFormat.TLV.getName(), null, this.config.getTimeout(), null); + lwM2mTransportRequest.sendAllRequest(client, target, typeOper, + null, this.config.getTimeout(), null); } else if (OBSERVE.equals(typeOper)) { - lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - null, null, this.config.getTimeout(), null); + lwM2mTransportRequest.sendAllRequest(client, target, typeOper, + null, this.config.getTimeout(), null); } } }); @@ -1113,7 +1106,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param attributeLwm2mNew - * @return */ - private void getAnalyzerAttributeLwm2m(Set registrationIds, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) { + private void getAnalyzerAttributeLwm2m(List clients, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) { ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); ConcurrentHashMap lwm2mAttributesOld = new Gson().fromJson(attributeLwm2mOld.toString(), new TypeToken>() { @@ -1135,23 +1128,22 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler // #6 // #6.2 if (analyzerParameters.getPathPostParametersAdd().size() > 0) { - registrationIds.forEach(registrationId -> { - Registration registration = this.clientContext.getRegistration(registrationId); - Set clientObjects = clientContext.getSupportedIdVerInClient(registration); + clients.forEach(client -> { + Set clientObjects = clientContext.getSupportedIdVerInClient(client); Set pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { ConcurrentHashMap finalParams = lwm2mAttributesNew; - pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), + pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(client, target, WRITE_ATTRIBUTES, finalParams.get(target), this.config.getTimeout(), null)); } }); } // #6.2 if (analyzerParameters.getPathPostParametersDel().size() > 0) { - registrationIds.forEach(registrationId -> { - Registration registration = this.clientContext.getRegistration(registrationId); - Set clientObjects = clientContext.getSupportedIdVerInClient(registration); + clients.forEach(client -> { + Registration registration = client.getRegistration(); + Set clientObjects = clientContext.getSupportedIdVerInClient(client); Set pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { @@ -1159,8 +1151,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler Map params = (Map) lwm2mAttributesOld.get(target); params.clear(); params.put(OBJECT_VERSION, ""); - lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), - params, this.config.getTimeout(), null); + lwM2mTransportRequest.sendAllRequest(client, target, WRITE_ATTRIBUTES, params, this.config.getTimeout(), null); }); } }); @@ -1168,12 +1159,10 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } - private void cancelObserveFromProfile(Registration registration, Set paramAnallyzer) { - LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); + private void cancelObserveFromProfile(LwM2mClient lwM2mClient, Set paramAnallyzer) { paramAnallyzer.forEach(pathIdVer -> { - if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) { - lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, OBSERVE_CANCEL, null, - null, this.config.getTimeout(), null); + if (this.getResourceValueFromLwM2MClient(lwM2mClient, pathIdVer) != null) { + lwM2mTransportRequest.sendAllRequest(lwM2mClient, pathIdVer, OBSERVE_CANCEL, null, this.config.getTimeout(), null); } } ); @@ -1181,14 +1170,12 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) { if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { - lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, WRITE_REPLACE, - ContentFormat.TLV.getName(), valueNew, - this.config.getTimeout(), null); + lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, WRITE_REPLACE, valueNew, this.config.getTimeout(), null); } else { log.error("Failed update resource [{}] [{}]", path, valueNew); String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", LOG_LW2M_ERROR, path, valueNew); - this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId()); + this.sendLogsToThingsboard(lwM2MClient, logMsg); log.info("Failed update resource [{}] [{}]", path, valueNew); } } @@ -1210,7 +1197,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ public String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { LwM2mClientProfile profile = clientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - LwM2mClient lwM2mClient = clientContext.getClient(sessionInfo); + LwM2mClient lwM2mClient = clientContext.getClientBySessionInfo(sessionInfo); return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() .filter(e -> e.getValue().getAsString().equals(name) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().map(Map.Entry::getKey) .orElse(null); @@ -1245,37 +1232,37 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param sessionInfo */ public void updateAttributeFromThingsboard(List tsKvProtos, TransportProtos.SessionInfoProto sessionInfo) { - LwM2mClient lwM2MClient = clientContext.getClient(sessionInfo); - tsKvProtos.forEach(tsKvProto -> { - String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, tsKvProto.getKv().getKey()); - if (pathIdVer != null) { - // #1.1 - if (lwM2MClient.getDelayedRequests().containsKey(pathIdVer) && tsKvProto.getTs() > lwM2MClient.getDelayedRequests().get(pathIdVer).getTs()) { - lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); - } else if (!lwM2MClient.getDelayedRequests().containsKey(pathIdVer)) { - lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); + LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo); + if (lwM2MClient != null) { + log.warn("1) UpdateAttributeFromThingsboard, tsKvProtos [{}]", tsKvProtos); + tsKvProtos.forEach(tsKvProto -> { + String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, tsKvProto.getKv().getKey()); + if (pathIdVer != null) { + // #1.1 + if (lwM2MClient.getDelayedRequests().containsKey(pathIdVer) && tsKvProto.getTs() > lwM2MClient.getDelayedRequests().get(pathIdVer).getTs()) { + lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); + } else if (!lwM2MClient.getDelayedRequests().containsKey(pathIdVer)) { + lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); + } } - } - }); - // #2.1 - lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> { - this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), - getValueFromKvProto(tsKvProto.getKv()), pathIdVer); - }); + }); + // #2.1 + lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> { + this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), + getValueFromKvProto(tsKvProto.getKv()), pathIdVer); + }); + } else { + log.error("UpdateAttributeFromThingsboard, lwM2MClient is null"); + } } /** * @param lwM2MClient - * @return SessionInfoProto - */ - private SessionInfoProto getSessionInfoOrCloseSession(LwM2mClient lwM2MClient) { - if (lwM2MClient != null) { - SessionInfoProto sessionInfoProto = lwM2MClient.getSession(); - if (sessionInfoProto == null) { - log.info("[{}] [{}]", lwM2MClient.getEndpoint(), CLIENT_NOT_AUTHORIZED); - this.closeClientSession(lwM2MClient.getRegistration()); - } - return sessionInfoProto; + private SessionInfoProto getSessionInfo(LwM2mClient lwM2MClient) { + if (lwM2MClient != null && lwM2MClient.getSession() != null) { + return lwM2MClient.getSession(); } return null; } @@ -1285,15 +1272,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @return - sessionInfo after access connect client */ public SessionInfoProto getSessionInfoOrCloseSession(Registration registration) { - return getSessionInfoOrCloseSession(clientContext.getOrRegister(registration)); - } - - /** - * @param registrationId - - * @return - - */ - private SessionInfoProto getSessionInfoOrCloseSession(String registrationId) { - return getSessionInfoOrCloseSession(clientContext.getClientByRegistrationId(registrationId)); + return getSessionInfo(clientContext.getClientByEndpoint(registration.getEndpoint())); } /** @@ -1323,7 +1302,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param lwM2MClient - LwM2M Client */ public void putDelayedUpdateResourcesThingsboard(LwM2mClient lwM2MClient) { - SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); + SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); if (sessionInfo != null) { //#1.1 ConcurrentMap keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); @@ -1340,22 +1319,44 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } } - public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) { - if (lwM2MClient.getRegistration().getSupportedVersion(FW_ID) != null) { - SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); + public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient, LwM2mClientRpcRequest rpcRequest) { + if (lwM2MClient.getRegistration().getSupportedVersion(FW_5_ID) != null) { + SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); if (sessionInfo != null) { - transportService.process(sessionInfo, createFirmwareRequestMsg(sessionInfo, FirmwareType.FIRMWARE.name()), + DefaultLwM2MTransportMsgHandler handler = this; + this.transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.FIRMWARE.name()), new TransportServiceCallback<>() { @Override - public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) { + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) - && response.getType().equals(FirmwareType.FIRMWARE.name())) { - lwM2MClient.getFwUpdate().setCurrentVersion(response.getVersion()); - lwM2MClient.getFwUpdate().setCurrentTitle(response.getTitle()); - lwM2MClient.getFwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId()); - lwM2MClient.getFwUpdate().sendReadObserveInfo(lwM2mTransportRequest); + && response.getType().equals(OtaPackageType.FIRMWARE.name())) { + LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(clientContext); + if (rpcRequest != null) { + fwUpdate.setStateUpdate(INITIATED.name()); + } + if (!FAILED.name().equals(fwUpdate.getStateUpdate())) { + log.warn("7) firmware start with ver: [{}]", response.getVersion()); + fwUpdate.setRpcRequest(rpcRequest); + fwUpdate.setCurrentVersion(response.getVersion()); + fwUpdate.setCurrentTitle(response.getTitle()); + fwUpdate.setCurrentId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())); + if (rpcRequest == null) { + fwUpdate.sendReadObserveInfo(lwM2mTransportRequest); + } else { + fwUpdate.writeFwSwWare(handler, lwM2mTransportRequest); + } + } else { + String msgError = String.format("OtaPackage device: %s, version: %s, stateUpdate: %s", + lwM2MClient.getDeviceName(), response.getVersion(), fwUpdate.getStateUpdate()); + log.warn("7_1 [{}]", msgError); + } } else { - log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); + String msgError = String.format("OtaPackage device: %s, responseStatus: %s", + lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); + log.trace(msgError); + if (rpcRequest != null) { + sendErrorRpcResponse(rpcRequest, msgError, sessionInfo); + } } } @@ -1368,21 +1369,27 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } } - public void getInfoSoftwareUpdate(LwM2mClient lwM2MClient) { + public void getInfoSoftwareUpdate(LwM2mClient lwM2MClient, LwM2mClientRpcRequest rpcRequest) { if (lwM2MClient.getRegistration().getSupportedVersion(SW_ID) != null) { - SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); + SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); if (sessionInfo != null) { - DefaultLwM2MTransportMsgHandler serviceImpl = this; - transportService.process(sessionInfo, createFirmwareRequestMsg(sessionInfo, FirmwareType.SOFTWARE.name()), + DefaultLwM2MTransportMsgHandler handler = this; + transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.SOFTWARE.name()), new TransportServiceCallback<>() { @Override - public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) { + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) - && response.getType().equals(FirmwareType.SOFTWARE.name())) { + && response.getType().equals(OtaPackageType.SOFTWARE.name())) { + lwM2MClient.getSwUpdate().setRpcRequest(rpcRequest); lwM2MClient.getSwUpdate().setCurrentVersion(response.getVersion()); lwM2MClient.getSwUpdate().setCurrentTitle(response.getTitle()); - lwM2MClient.getSwUpdate().setCurrentId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId()); + lwM2MClient.getSwUpdate().setCurrentId(new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())).getId()); lwM2MClient.getSwUpdate().sendReadObserveInfo(lwM2mTransportRequest); + if (rpcRequest == null) { + lwM2MClient.getSwUpdate().sendReadObserveInfo(lwM2mTransportRequest); + } else { + lwM2MClient.getSwUpdate().writeFwSwWare(handler, lwM2mTransportRequest); + } } else { log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); } @@ -1397,8 +1404,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } } - private TransportProtos.GetFirmwareRequestMsg createFirmwareRequestMsg(SessionInfoProto sessionInfo, String nameFwSW) { - return TransportProtos.GetFirmwareRequestMsg.newBuilder() + private TransportProtos.GetOtaPackageRequestMsg createOtaPackageRequestMsg(SessionInfoProto sessionInfo, String nameFwSW) { + return TransportProtos.GetOtaPackageRequestMsg.newBuilder() .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) .setTenantIdMSB(sessionInfo.getTenantIdMSB()) 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 90e3e13033..9be164023d 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 @@ -28,7 +28,6 @@ import org.eclipse.leshan.server.californium.registration.CaliforniumRegistratio import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.springframework.stereotype.Component; -import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -36,6 +35,8 @@ import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MAuthorizer; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; @@ -58,14 +59,13 @@ import java.security.spec.InvalidParameterSpecException; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Arrays; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8; import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; @Slf4j @Component @@ -81,9 +81,9 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final LwM2mTransportServerHelper helper; - private final LwM2mTransportMsgHandler handler; + private final DefaultLwM2MTransportMsgHandler handler; private final CaliforniumRegistrationStore registrationStore; - private final EditableSecurityStore securityStore; + private final TbSecurityStore securityStore; private final LwM2mClientContext lwM2mClientContext; private final TbLwM2MDtlsCertificateVerifier certificateVerifier; private final TbLwM2MAuthorizer authorizer; @@ -96,6 +96,17 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { new LWM2MGenerationPSkRPkECC(); } this.server = getLhServer(); + /** + * Add a resource to the server. + * CoapResource -> + * path = FW_PACKAGE or SW_PACKAGE + * nameFile = "BC68JAR01A09_TO_BC68JAR01A10.bin" + * "coap://host:port/{path}/{token}/{nameFile}" + */ + + + LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(handler, FIRMWARE_UPDATE_COAP_RECOURSE); + this.server.coap().getServer().add(otaCoapResource); this.startLhServer(); this.context.setServer(server); } @@ -126,7 +137,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); /* Create CoAP Config */ - builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort())); + builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort(), config)); /* Define model provider (Create Models )*/ LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.helper, this.context); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java index 280cb7dde4..00f70dbcf7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java @@ -16,10 +16,14 @@ package org.thingsboard.server.transport.lwm2m.server; import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.core.network.config.NetworkConfigDefaults; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; + +import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; public class LwM2mNetworkConfig { - public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort) { + public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort, LwM2MTransportServerConfig config) { NetworkConfig coapConfig = new NetworkConfig(); coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec); coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort); @@ -49,8 +53,15 @@ public class LwM2mNetworkConfig { - value of true indicate that the server will response with block2 option event if no further blocks are required. */ coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); - - coapConfig.setInt(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, 300000); + /** + * The maximum amount of time (in milliseconds) allowed between + * transfers of individual blocks in a blockwise transfer before the + * blockwise transfer state is discarded. + *

+ * The default value of this property is + * {@link NetworkConfigDefaults#DEFAULT_BLOCKWISE_STATUS_LIFETIME} = 5 * 60 * 1000; // 5 mins [ms]. + */ + coapConfig.setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME); /** !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13 The maximum size of a resource body (in bytes) that will be accepted diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOtaConvert.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOtaConvert.java new file mode 100644 index 0000000000..7b7c56adb3 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOtaConvert.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server; + +import lombok.Data; +import org.eclipse.leshan.core.model.ResourceModel; + +@Data +public class LwM2mOtaConvert { + private ResourceModel.Type currentType; + private Object value; +} 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 f0e11aceb4..b69825843e 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 @@ -87,7 +87,7 @@ public class LwM2mServerListener { @Override public void cancelled(Observation observation) { String msg = String.format("%s: Canceled Observation %s.", LOG_LW2M_INFO, observation.getPath()); - service.sendLogsToThingsboard(msg, observation.getRegistrationId()); + service.sendLogsToThingsboard(observation.getRegistrationId(), msg); log.warn(msg); } @@ -109,7 +109,7 @@ public class LwM2mServerListener { String msg = String.format("%s: Successful start newObservation %s.", LOG_LW2M_INFO, observation.getPath()); log.warn(msg); - service.sendLogsToThingsboard(msg, registration.getId()); + service.sendLogsToThingsboard(registration.getId(), msg); } }; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java new file mode 100644 index 0000000000..1236c8b6d2 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java @@ -0,0 +1,158 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.observe.ObserveRelation; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.eclipse.californium.core.server.resources.ResourceObserver; + +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SOFTWARE_UPDATE_COAP_RECOURSE; + +@Slf4j +public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { + private final ConcurrentMap tokenToObserveRelationMap = new ConcurrentHashMap<>(); + private final ConcurrentMap tokenToObserveNotificationSeqMap = new ConcurrentHashMap<>(); + private final LwM2mTransportMsgHandler handler; + + public LwM2mTransportCoapResource(LwM2mTransportMsgHandler handler, String name) { + super(name); + this.handler = handler; + this.setObservable(true); // enable observing + this.addObserver(new CoapResourceObserver()); + } + + + @Override + public void checkObserveRelation(Exchange exchange, Response response) { + String token = getTokenFromRequest(exchange.getRequest()); + final ObserveRelation relation = exchange.getRelation(); + if (relation == null || relation.isCanceled()) { + return; // because request did not try to establish a relation + } + if (CoAP.ResponseCode.isSuccess(response.getCode())) { + + if (!relation.isEstablished()) { + relation.setEstablished(); + addObserveRelation(relation); + } + AtomicInteger notificationCounter = tokenToObserveNotificationSeqMap.computeIfAbsent(token, s -> new AtomicInteger(0)); + response.getOptions().setObserve(notificationCounter.getAndIncrement()); + } // ObserveLayer takes care of the else case + } + + + @Override + protected void processHandleGet(CoapExchange exchange) { + log.warn("90) processHandleGet [{}]", exchange); + if (exchange.getRequestOptions().getUriPath().size() >= 2 && + (FIRMWARE_UPDATE_COAP_RECOURSE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size()-2)) || + SOFTWARE_UPDATE_COAP_RECOURSE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size()-2)))) { + this.sendOtaData(exchange); + } + } + + @Override + protected void processHandlePost(CoapExchange exchange) { + log.warn("2) processHandleGet [{}]", exchange); + } + + /** + * Override the default behavior so that requests to sub resources (typically /{name}/{token}) are handled by + * /name resource. + */ + @Override + public Resource getChild(String name) { + return this; + } + + + private String getTokenFromRequest(Request request) { + return (request.getSourceContext() != null ? request.getSourceContext().getPeerAddress().getAddress().getHostAddress() : "null") + + ":" + (request.getSourceContext() != null ? request.getSourceContext().getPeerAddress().getPort() : -1) + ":" + request.getTokenString(); + } + + public class CoapResourceObserver implements ResourceObserver { + + @Override + public void changedName(String old) { + + } + + @Override + public void changedPath(String old) { + + } + + @Override + public void addedChild(Resource child) { + + } + + @Override + public void removedChild(Resource child) { + + } + + @Override + public void addedObserveRelation(ObserveRelation relation) { + + } + + @Override + public void removedObserveRelation(ObserveRelation relation) { + + } + } + + private void sendOtaData(CoapExchange exchange) { + String idStr = exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size()-1 + ); + UUID currentId = UUID.fromString(idStr); + Response response = new Response(CoAP.ResponseCode.CONTENT); + byte[] fwData = this.getOtaData(currentId); + log.warn("91) read softWare data (length): [{}]", fwData.length); + if (fwData != null && fwData.length > 0) { + response.setPayload(fwData); + if (exchange.getRequestOptions().getBlock2() != null) { + int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); + boolean moreFlag = fwData.length > chunkSize; + response.getOptions().setBlock2(chunkSize, moreFlag, 0); + log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, moreFlag); + } + else { + log.warn("92) with block1 Send currentId: [{}], length: [{}], ", currentId.toString(), fwData.length); + } + exchange.respond(response); + } + } + + private byte[] getOtaData(UUID currentId) { + return ((DefaultLwM2MTransportMsgHandler) handler).otaPackageDataCache.get(currentId.toString()); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java index 794df65db5..ee3b07da34 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java @@ -22,7 +22,8 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcRequest; import java.util.Collection; import java.util.Optional; @@ -39,7 +40,7 @@ public interface LwM2mTransportMsgHandler { void setCancelObservationsAll(Registration registration); - void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest); + void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, LwM2mClientRpcRequest rpcRequest); void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo); @@ -57,13 +58,13 @@ public interface LwM2mTransportMsgHandler { void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse); - void doTrigger(Registration registration, String path); - void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); void onAwakeDev(Registration registration); - void sendLogsToThingsboard(String msg, String registrationId); + void sendLogsToThingsboard(LwM2mClient client, String msg); + + void sendLogsToThingsboard(String registrationId, String msg); LwM2MTransportServerConfig getConfig(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index e5cd289758..3568000ead 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -32,10 +32,10 @@ import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; -import org.eclipse.leshan.core.request.DownlinkRequest; import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ReadRequest; +import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.exception.ClientSleepingException; @@ -55,7 +55,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; -import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; @@ -71,21 +71,19 @@ import java.util.stream.Collectors; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADED; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.FAILED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getContentFormatByResourceModelType; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; @@ -118,34 +116,32 @@ public class LwM2mTransportRequest { new NamedThreadFactory(String.format("LwM2M %s channel response after request", RESPONSE_REQUEST_CHANNEL))); } - /** - * Device management and service enablement, including Read, Write, Execute, Discover, Create, Delete and Write-Attributes - * - * @param registration - - * @param targetIdVer - - * @param typeOper - - * @param contentFormatName - - */ + public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { + sendAllRequest(lwM2MClient, targetIdVer, typeOper, lwM2MClient.getDefaultContentFormat(), params, timeoutInMs, lwm2mClientRpcRequest); + } - public void sendAllRequest(Registration registration, String targetIdVer, LwM2mTypeOper typeOper, - String contentFormatName, Object params, long timeoutInMs, Lwm2mClientRpcRequest lwm2mClientRpcRequest) { + + public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, + ContentFormat contentFormat, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { + Registration registration = lwM2MClient.getRegistration(); try { String target = convertPathFromIdVerToObjectId(targetIdVer); - ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT; - LwM2mClient lwM2MClient = this.lwM2mClientContext.getOrRegister(registration); + if(contentFormat == null){ + contentFormat = ContentFormat.DEFAULT; + } LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; - if (!OBSERVE_READ_ALL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) { + if (!OBSERVE_CANCEL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) { if (lwM2MClient.isValidObjectVersion(targetIdVer)) { timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; - DownlinkRequest request = createRequest(registration, lwM2MClient, typeOper, contentFormat, target, + SimpleDownlinkRequest request = createRequest(registration, lwM2MClient, typeOper, contentFormat, target, targetIdVer, resultIds, params, lwm2mClientRpcRequest); if (request != null) { try { this.sendRequest(registration, lwM2MClient, request, timeoutInMs, lwm2mClientRpcRequest); } catch (ClientSleepingException e) { - DownlinkRequest finalRequest = request; + SimpleDownlinkRequest finalRequest = request; long finalTimeoutInMs = timeoutInMs; - Lwm2mClientRpcRequest finalRpcRequest = lwm2mClientRpcRequest; + LwM2mClientRpcRequest finalRpcRequest = lwm2mClientRpcRequest; lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, finalRpcRequest)); } catch (Exception e) { log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); @@ -153,63 +149,82 @@ public class LwM2mTransportRequest { } else if (WRITE_UPDATE.name().equals(typeOper.name())) { if (lwm2mClientRpcRequest != null) { String errorMsg = String.format("Path %s params is not valid", targetIdVer); - handler.sentRpcRequest(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); + handler.sentRpcResponse(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); } } else if (WRITE_REPLACE.name().equals(typeOper.name()) || EXECUTE.name().equals(typeOper.name())) { if (lwm2mClientRpcRequest != null) { String errorMsg = String.format("Path %s object model is absent", targetIdVer); - handler.sentRpcRequest(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); + handler.sentRpcResponse(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); } } else if (!OBSERVE_CANCEL.name().equals(typeOper.name())) { log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); if (lwm2mClientRpcRequest != null) { ResourceModel resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null"; - this.handler.sentRpcRequest(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); + handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); } } } else if (lwm2mClientRpcRequest != null) { String errorMsg = String.format("Path %s not found in object version", targetIdVer); - this.handler.sentRpcRequest(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); - } - } else if (OBSERVE_READ_ALL.name().equals(typeOper.name()) || DISCOVER_ALL.name().equals(typeOper.name())) { - Set paths; - if (OBSERVE_READ_ALL.name().equals(typeOper.name())) { - Set observations = context.getServer().getObservationService().getObservations(registration); - paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); - } else { - assert registration != null; - Link[] objectLinks = registration.getSortedObjectLinks(); - paths = Arrays.stream(objectLinks).map(Link::toString).collect(Collectors.toUnmodifiableSet()); + handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); } - String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO, - typeOper.name(), paths); - this.handler.sendLogsToThingsboard(msg, registration.getId()); - if (lwm2mClientRpcRequest != null) { - String valueMsg = String.format("Paths - %s", paths); - this.handler.sentRpcRequest(lwm2mClientRpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); + } else { + switch (typeOper) { + case OBSERVE_READ_ALL: + case DISCOVER_ALL: + Set paths; + if (OBSERVE_READ_ALL.name().equals(typeOper.name())) { + Set observations = context.getServer().getObservationService().getObservations(registration); + paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); + } else { + assert registration != null; + Link[] objectLinks = registration.getSortedObjectLinks(); + paths = Arrays.stream(objectLinks).map(Link::toString).collect(Collectors.toUnmodifiableSet()); + } + String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO, + typeOper.name(), paths); + this.handler.sendLogsToThingsboard(lwM2MClient, msg); + if (lwm2mClientRpcRequest != null) { + String valueMsg = String.format("Paths - %s", paths); + handler.sentRpcResponse(lwm2mClientRpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); + } + break; + case OBSERVE_CANCEL: + case OBSERVE_CANCEL_ALL: + int observeCancelCnt = 0; + String observeCancelMsg = null; + if (OBSERVE_CANCEL.name().equals(typeOper)) { + observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration, target); + observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO, + OBSERVE_CANCEL.name(), target, observeCancelCnt); + } else { + observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration); + observeCancelMsg = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO, + OBSERVE_CANCEL.name(), observeCancelCnt); + } + this.afterObserveCancel(lwM2MClient, observeCancelCnt, observeCancelMsg, lwm2mClientRpcRequest); + break; + // lwm2mClientRpcRequest != null + case FW_UPDATE: + handler.getInfoFirmwareUpdate(lwM2MClient, lwm2mClientRpcRequest); + break; } - } else if (OBSERVE_CANCEL_ALL.name().equals(typeOper.name())) { - int observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration); - String observeCancelMsgAll = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO, - OBSERVE_CANCEL.name(), observeCancelCnt); - this.afterObserveCancel(registration, observeCancelCnt, observeCancelMsgAll, lwm2mClientRpcRequest); } } catch (Exception e) { String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR, typeOper.name(), e.getMessage()); - handler.sendLogsToThingsboard(msg, registration.getId()); + handler.sendLogsToThingsboard(lwM2MClient, msg); if (lwm2mClientRpcRequest != null) { String errorMsg = String.format("Path %s type operation %s %s", targetIdVer, typeOper.name(), e.getMessage()); - handler.sentRpcRequest(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); + handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); } } } - private DownlinkRequest createRequest(Registration registration, LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, + private SimpleDownlinkRequest createRequest(Registration registration, LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, ContentFormat contentFormat, String target, String targetIdVer, - LwM2mPath resultIds, Object params, Lwm2mClientRpcRequest rpcRequest) { - DownlinkRequest request = null; + LwM2mPath resultIds, Object params, LwM2mClientRpcRequest rpcRequest) { + SimpleDownlinkRequest request = null; switch (typeOper) { case READ: request = new ReadRequest(contentFormat, target); @@ -234,17 +249,6 @@ public class LwM2mTransportRequest { request = new ObserveRequest(contentFormat, resultIds.getObjectId()); } break; - case OBSERVE_CANCEL: - /* - lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); - At server side this will not remove the observation from the observation store, to do it you need to use - {@code ObservationService#cancelObservation()} - */ - int observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration, target); - String observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO, - OBSERVE_CANCEL.name(), target, observeCancelCnt); - this.afterObserveCancel(registration, observeCancelCnt, observeCancelMsg, rpcRequest); - break; case EXECUTE: ResourceModel resourceModelExecute = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); if (resourceModelExecute != null) { @@ -267,7 +271,7 @@ public class LwM2mTransportRequest { contentFormat = getContentFormatByResourceModelType(resourceModelWrite, contentFormat); request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModelWrite.type, - registration, rpcRequest); + lwM2MClient, rpcRequest); } break; case WRITE_UPDATE: @@ -323,19 +327,19 @@ public class LwM2mTransportRequest { */ @SuppressWarnings({"error sendRequest"}) - private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, - long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { + private void sendRequest(Registration registration, LwM2mClient lwM2MClient, SimpleDownlinkRequest request, + long timeoutInMs, LwM2mClientRpcRequest rpcRequest) { context.getServer().send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { - this.handleResponse(registration, request.getPath().toString(), response, request, rpcRequest); + this.handleResponse(lwM2MClient, request.getPath().toString(), response, request, rpcRequest); } else { String msg = String.format("%s: SendRequest %s: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(), ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); - handler.sendLogsToThingsboard(msg, registration.getId()); + handler.sendLogsToThingsboard(lwM2MClient, msg); log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); if (!lwM2MClient.isInit()) { @@ -343,18 +347,18 @@ public class LwM2mTransportRequest { } /** Not Found */ if (rpcRequest != null) { - handler.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); + handler.sentRpcResponse(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); } /** Not Found set setClient_fw_info... = empty **/ - if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { + if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); } - if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { + if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { lwM2MClient.getSwUpdate().initReadValue(handler, this, request.getPath().toString()); } - if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { + if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { this.afterWriteFwSWUpdateError(registration, request, response.getErrorMessage()); } if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) { @@ -365,13 +369,13 @@ public class LwM2mTransportRequest { /** version == null set setClient_fw_info... = empty **/ - if (lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { + if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); } - if (lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { + if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { lwM2MClient.getSwUpdate().initReadValue(handler, this, request.getPath().toString()); } - if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { + if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { this.afterWriteFwSWUpdateError(registration, request, e.getMessage()); } if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) { @@ -382,17 +386,17 @@ public class LwM2mTransportRequest { } String msg = String.format("%s: SendRequest %s: Resource path - %s msg error - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(), request.getPath().toString(), e.getMessage()); - handler.sendLogsToThingsboard(msg, registration.getId()); + handler.sendLogsToThingsboard(lwM2MClient, msg); log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString()); if (rpcRequest != null) { - handler.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR); + handler.sentRpcResponse(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR); } }); } private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type, - Registration registration, Lwm2mClientRpcRequest rpcRequest) { + LwM2mClient client, LwM2mClientRpcRequest rpcRequest) { try { if (type != null) { switch (type) { @@ -427,23 +431,23 @@ public class LwM2mTransportRequest { String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client", patn, type, value, e.toString()); - handler.sendLogsToThingsboard(msg, registration.getId()); + handler.sendLogsToThingsboard(client, msg); log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); if (rpcRequest != null) { String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value); - handler.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); + handler.sentRpcResponse(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); } return null; } } - private void handleResponse(Registration registration, final String path, LwM2mResponse response, - DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) { + private void handleResponse(LwM2mClient lwM2mClient, final String path, LwM2mResponse response, + SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { responseRequestExecutor.submit(() -> { try { - this.sendResponse(registration, path, response, request, rpcRequest); + this.sendResponse(lwM2mClient, path, response, request, rpcRequest); } catch (Exception e) { - log.error("[{}] endpoint [{}] path [{}] Exception Unable to after send response.", registration.getEndpoint(), path, e); + log.error("[{}] endpoint [{}] path [{}] Exception Unable to after send response.", lwM2mClient.getRegistration().getEndpoint(), path, e); } }); } @@ -451,56 +455,61 @@ public class LwM2mTransportRequest { /** * processing a response from a client * - * @param registration - * @param path - * @param response - */ - private void sendResponse(Registration registration, String path, LwM2mResponse response, - DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) { + private void sendResponse(LwM2mClient lwM2mClient, String path, LwM2mResponse response, + SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { + Registration registration = lwM2mClient.getRegistration(); String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); String msgLog = ""; if (response instanceof ReadResponse) { handler.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); } else if (response instanceof DeleteResponse) { - log.warn("[{}] Path [{}] DeleteResponse 5_Send", pathIdVer, response); + log.warn("11) [{}] Path [{}] DeleteResponse", pathIdVer, response); + if (rpcRequest != null) { + rpcRequest.setInfoMsg(null); + handler.sentRpcResponse(rpcRequest, response.getCode().getName(), null, null); + } } else if (response instanceof DiscoverResponse) { - String discoverValue = Link.serialize(((DiscoverResponse)response).getObjectLinks()); + String discoverValue = Link.serialize(((DiscoverResponse) response).getObjectLinks()); msgLog = String.format("%s: type operation: %s path: %s value: %s", LOG_LW2M_INFO, DISCOVER.name(), request.getPath().toString(), discoverValue); - handler.sendLogsToThingsboard(msgLog, registration.getId()); + handler.sendLogsToThingsboard(lwM2mClient, msgLog); log.warn("DiscoverResponse: [{}]", (DiscoverResponse) response); if (rpcRequest != null) { - handler.sentRpcRequest(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE); + handler.sentRpcResponse(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE); } } else if (response instanceof ExecuteResponse) { - log.warn("[{}] Path [{}] ExecuteResponse 7_Send", pathIdVer, response); + msgLog = String.format("%s: type operation: %s path: %s", + LOG_LW2M_INFO, EXECUTE.name(), request.getPath().toString()); + log.warn("9) [{}] ", msgLog); + handler.sendLogsToThingsboard(lwM2mClient, msgLog); + if (rpcRequest != null) { + msgLog = String.format("Start %s path: %S. Preparation finished: %s", EXECUTE.name(), path, rpcRequest.getInfoMsg()); + rpcRequest.setInfoMsg(msgLog); + handler.sentRpcResponse(rpcRequest, response.getCode().getName(), path, LOG_LW2M_INFO); + } + } else if (response instanceof WriteAttributesResponse) { msgLog = String.format("%s: type operation: %s path: %s value: %s", LOG_LW2M_INFO, WRITE_ATTRIBUTES.name(), request.getPath().toString(), ((WriteAttributesRequest) request).getAttributes().toString()); - handler.sendLogsToThingsboard(msgLog, registration.getId()); - log.warn("[{}] Path [{}] WriteAttributesResponse 8_Send", pathIdVer, response); + handler.sendLogsToThingsboard(lwM2mClient, msgLog); + log.warn("12) [{}] Path [{}] WriteAttributesResponse", pathIdVer, response); if (rpcRequest != null) { - handler.sentRpcRequest(rpcRequest, response.getCode().getName(), response.toString(), LOG_LW2M_VALUE); + handler.sentRpcResponse(rpcRequest, response.getCode().getName(), response.toString(), LOG_LW2M_VALUE); } } else if (response instanceof WriteResponse) { - log.warn("[{}] Path [{}] WriteResponse 9_Send", pathIdVer, response); - this.infoWriteResponse(registration, response, request); + msgLog = String.format("Type operation: Write path: %s", pathIdVer); + log.warn("10) [{}] response: [{}]", msgLog, response); + this.infoWriteResponse(lwM2mClient, response, request, rpcRequest); handler.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request); } - if (rpcRequest != null) { - if (response instanceof ExecuteResponse - || response instanceof WriteAttributesResponse - || response instanceof DeleteResponse) { - rpcRequest.setInfoMsg(null); - handler.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null); - } else if (response instanceof WriteResponse) { - handler.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO); - } - } } - private void infoWriteResponse(Registration registration, LwM2mResponse response, DownlinkRequest request) { + private void infoWriteResponse(LwM2mClient lwM2mClient, LwM2mResponse response, SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { try { + Registration registration = lwM2mClient.getRegistration(); LwM2mNode node = ((WriteRequest) request).getNode(); String msg = null; Object value; @@ -517,12 +526,12 @@ public class LwM2mTransportRequest { if (singleResource.getType() == ResourceModel.Type.STRING) { valueLength = ((String) singleResource.getValue()).length(); value = ((String) singleResource.getValue()) - .substring(Math.min(valueLength, config.getLogMaxLength())); + .substring(Math.min(valueLength, config.getLogMaxLength())).trim(); } else { valueLength = ((byte[]) singleResource.getValue()).length; value = new String(Arrays.copyOf(((byte[]) singleResource.getValue()), - Math.min(valueLength, config.getLogMaxLength()))); + Math.min(valueLength, config.getLogMaxLength()))).trim(); } value = valueLength > config.getLogMaxLength() ? value + "..." : value; msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path: %s length: %s value: %s", @@ -535,9 +544,15 @@ public class LwM2mTransportRequest { } } if (msg != null) { - handler.sendLogsToThingsboard(msg, registration.getId()); - if (request.getPath().toString().equals(FW_PACKAGE_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { + handler.sendLogsToThingsboard(lwM2mClient, msg); + if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { this.afterWriteSuccessFwSwUpdate(registration, request); + if (rpcRequest != null) { + rpcRequest.setInfoMsg(msg); + } + } + else if (rpcRequest != null) { + handler.sentRpcResponse(rpcRequest, response.getCode().getName(), msg, LOG_LW2M_INFO); } } } catch (Exception e) { @@ -550,24 +565,24 @@ public class LwM2mTransportRequest { * fw_state/sw_state = DOWNLOADED * send operation Execute */ - private void afterWriteSuccessFwSwUpdate(Registration registration, DownlinkRequest request) { + private void afterWriteSuccessFwSwUpdate(Registration registration, SimpleDownlinkRequest request) { LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_PACKAGE_ID) && lwM2MClient.getFwUpdate() != null) { + if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && lwM2MClient.getFwUpdate() != null) { lwM2MClient.getFwUpdate().setStateUpdate(DOWNLOADED.name()); lwM2MClient.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); } if (request.getPath().toString().equals(SW_PACKAGE_ID) && lwM2MClient.getSwUpdate() != null) { lwM2MClient.getSwUpdate().setStateUpdate(DOWNLOADED.name()); - lwM2MClient.getSwUpdate().sendLogs(this.handler,WRITE_REPLACE.name(), LOG_LW2M_INFO, null); + lwM2MClient.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); } } /** * After finish operation FwSwUpdate Write (error): fw_state = FAILED */ - private void afterWriteFwSWUpdateError(Registration registration, DownlinkRequest request, String msgError) { + private void afterWriteFwSWUpdateError(Registration registration, SimpleDownlinkRequest request, String msgError) { LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_PACKAGE_ID) && lwM2MClient.getFwUpdate() != null) { + if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && lwM2MClient.getFwUpdate() != null) { lwM2MClient.getFwUpdate().setStateUpdate(FAILED.name()); lwM2MClient.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); } @@ -577,7 +592,7 @@ public class LwM2mTransportRequest { } } - private void afterExecuteFwSwUpdateError(Registration registration, DownlinkRequest request, String msgError) { + private void afterExecuteFwSwUpdateError(Registration registration, SimpleDownlinkRequest request, String msgError) { LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); if (request.getPath().toString().equals(FW_UPDATE_ID) && lwM2MClient.getFwUpdate() != null) { lwM2MClient.getFwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); @@ -587,12 +602,12 @@ public class LwM2mTransportRequest { } } - private void afterObserveCancel(Registration registration, int observeCancelCnt, String observeCancelMsg, Lwm2mClientRpcRequest rpcRequest) { - handler.sendLogsToThingsboard(observeCancelMsg, registration.getId()); + private void afterObserveCancel(LwM2mClient lwM2mClient, int observeCancelCnt, String observeCancelMsg, LwM2mClientRpcRequest rpcRequest) { + handler.sendLogsToThingsboard(lwM2mClient, observeCancelMsg); log.warn("[{}]", observeCancelMsg); if (rpcRequest != null) { rpcRequest.setInfoMsg(String.format("Count: %d", observeCancelCnt)); - handler.sentRpcRequest(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO); + handler.sentRpcResponse(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index fff35ea855..a52fc0158d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -53,6 +53,8 @@ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V; @@ -63,7 +65,12 @@ import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType. public class LwM2mTransportServerHelper { private final LwM2mTransportContext context; - private final LwM2MJsonAdaptor adaptor; + private final AtomicInteger atomicTs = new AtomicInteger(0); + + + public long getTS() { + return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) * 1000L + (atomicTs.getAndIncrement() % 1000); + } /** * send to Thingsboard Attribute || Telemetry @@ -96,7 +103,7 @@ public class LwM2mTransportServerHelper { public void sendParametersOnThingsboardTelemetry(List result, SessionInfoProto sessionInfo) { PostTelemetryMsg.Builder request = PostTelemetryMsg.newBuilder(); TransportProtos.TsKvListProto.Builder builder = TransportProtos.TsKvListProto.newBuilder(); - builder.setTs(System.currentTimeMillis()); + builder.setTs(this.getTS()); builder.addAllKv(result); request.addTsKvList(builder.build()); PostTelemetryMsg postTelemetryMsg = request.build(); @@ -128,7 +135,7 @@ public class LwM2mTransportServerHelper { public ObjectModel parseFromXmlToObjectModel(byte[] xmlByte, String streamName, DefaultDDFFileValidator ddfValidator) { try { DDFFileParser ddfFileParser = new DDFFileParser(ddfValidator); - return ddfFileParser.parseEx(new ByteArrayInputStream(xmlByte), streamName).get(0); + return ddfFileParser.parse(new ByteArrayInputStream(xmlByte), streamName).get(0); } catch (IOException | InvalidDDFFileException e) { log.error("Could not parse the XML file [{}]", streamName, e); return null; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java index 320059b0a7..1d1feab85e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java @@ -36,18 +36,18 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.codec.CodecException; -import org.eclipse.leshan.core.request.DownlinkRequest; +import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.registration.Registration; import org.nustaq.serialization.FSTConfiguration; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.firmware.FirmwareKey; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus; -import org.thingsboard.server.common.data.firmware.FirmwareUtil; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.OtaPackageKey; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; +import org.thingsboard.server.common.data.ota.OtaPackageUtil; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; @@ -77,18 +77,23 @@ import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING; import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADED; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.DOWNLOADING; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.FAILED; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATED; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATING; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.VERIFIED; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; @Slf4j public class LwM2mTransportUtil { + public static final String EVENT_AWAKE = "AWAKE"; + public static final String RESPONSE_REQUEST_CHANNEL = "RESP_REQ"; + public static final String RESPONSE_CHANNEL = "RESP"; + public static final String OBSERVE_CHANNEL = "OBSERVE"; + public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0"; public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; public static final String BOOTSTRAP = "bootstrap"; @@ -116,9 +121,6 @@ public class LwM2mTransportUtil { public static final String LOG_LW2M_WARN = "warn"; public static final String LOG_LW2M_VALUE = "value"; - public static final int LWM2M_STRATEGY_1 = 1; - public static final int LWM2M_STRATEGY_2 = 2; - public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized"; public static final String LWM2M_VERSION_DEFAULT = "1.0"; @@ -132,18 +134,23 @@ public class LwM2mTransportUtil { public static final String FINISH_VALUE_KEY = ","; public static final String START_JSON_KEY = "{"; public static final String FINISH_JSON_KEY = "}"; - // public static final String contentFormatNameKey = "contentFormatName"; public static final String INFO_KEY = "info"; - // public static final String TIME_OUT_IN_MS = "timeOutInMs"; public static final String RESULT_KEY = "result"; public static final String ERROR_KEY = "error"; public static final String METHOD_KEY = "methodName"; - // FirmWare + + // Firmware + public static final String FIRMWARE_UPDATE_COAP_RECOURSE = "firmwareUpdateCoapRecourse"; public static final String FW_UPDATE = "Firmware update"; - public static final Integer FW_ID = 5; + public static final Integer FW_5_ID = 5; + public static final Integer FW_19_ID = 19; + // Package W - public static final String FW_PACKAGE_ID = "/5/0/0"; + public static final String FW_PACKAGE_5_ID = "/5/0/0"; + public static final String FW_PACKAGE_19_ID = "/19/0/0"; + // Package URI + public static final String FW_PACKAGE_URI_ID = "/5/0/1"; // State R public static final String FW_STATE_ID = "/5/0/3"; // Update Result R @@ -151,15 +158,28 @@ public class LwM2mTransportUtil { // PkgName R public static final String FW_NAME_ID = "/5/0/6"; // PkgVersion R - public static final String FW_VER_ID = "/5/0/7"; + public static final String FW_5_VER_ID = "/5/0/7"; + /** + * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8 + * BC68JAR01A10 + * # Request prodct type number + * ATI + * Quectel + * BC68 + * Revision:BC68JAR01A10 + */ + public static final String FW_3_VER_ID = "/3/0/3"; // Update E public static final String FW_UPDATE_ID = "/5/0/2"; - // SoftWare + // Software + public static final String SOFTWARE_UPDATE_COAP_RECOURSE = "softwareUpdateCoapRecourse"; public static final String SW_UPDATE = "Software update"; public static final Integer SW_ID = 9; // Package W public static final String SW_PACKAGE_ID = "/9/0/2"; + // Package URI + public static final String SW_PACKAGE_URI_ID = "/9/0/3"; // Update State R public static final String SW_UPDATE_STATE_ID = "/9/0/7"; // Update Result R @@ -229,11 +249,12 @@ public class LwM2mTransportUtil { */ WRITE_UPDATE(9, "WriteUpdate"), WRITE_ATTRIBUTES(10, "WriteAttributes"), - DELETE(11, "Delete"); + DELETE(11, "Delete"), // only for RPC + FW_UPDATE(12, "FirmwareUpdate"); // FW_READ_INFO(12, "FirmwareReadInfo"), -// FW_UPDATE(13, "FirmwareUpdate"), + // SW_READ_INFO(15, "SoftwareReadInfo"), // SW_UPDATE(16, "SoftwareUpdate"), // SW_UNINSTALL(18, "SoftwareUninstall"); @@ -354,20 +375,11 @@ public class LwM2mTransportUtil { * FirmwareUpdateStatus { * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED */ - public static FirmwareUpdateStatus EqualsFwSateToFirmwareUpdateStatus(StateFw stateFw, UpdateResultFw updateResultFw) { + public static OtaPackageUpdateStatus equalsFwSateFwResultToFirmwareUpdateStatus(StateFw stateFw, UpdateResultFw updateResultFw) { switch (updateResultFw) { case INITIAL: - switch (stateFw) { - case IDLE: - return VERIFIED; - case DOWNLOADING: - return DOWNLOADING; - case DOWNLOADED: - return DOWNLOADED; - case UPDATING: - return UPDATING; - } - case UPDATE_SUCCESSFULLY: + return equalsFwSateToFirmwareUpdateStatus(stateFw); + case UPDATE_SUCCESSFULLY: return UPDATED; case NOT_ENOUGH: case OUT_OFF_MEMORY: @@ -383,6 +395,41 @@ public class LwM2mTransportUtil { } } + public static OtaPackageUpdateStatus equalsFwResultToFirmwareUpdateStatus(UpdateResultFw updateResultFw) { + switch (updateResultFw) { + case INITIAL: + return VERIFIED; + case UPDATE_SUCCESSFULLY: + return UPDATED; + case NOT_ENOUGH: + case OUT_OFF_MEMORY: + case CONNECTION_LOST: + case INTEGRITY_CHECK_FAILURE: + case UNSUPPORTED_TYPE: + case INVALID_URI: + case UPDATE_FAILED: + case UNSUPPORTED_PROTOCOL: + return FAILED; + default: + throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", updateResultFw.name()); + } + } + + public static OtaPackageUpdateStatus equalsFwSateToFirmwareUpdateStatus(StateFw stateFw) { + switch (stateFw) { + case IDLE: + return VERIFIED; + case DOWNLOADING: + return DOWNLOADING; + case DOWNLOADED: + return DOWNLOADED; + case UPDATING: + return UPDATING; + default: + throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", stateFw); + } + } + /** * SW Update State R * 0: INITIAL Before downloading. (see 5.1.2.1) @@ -496,11 +543,106 @@ public class LwM2mTransportUtil { } } + public enum LwM2MClientStrategy { + CLIENT_STRATEGY_1(1, "Read only resources marked as observation"), + CLIENT_STRATEGY_2(2, "Read all client resources"); + + public int code; + public String type; + + LwM2MClientStrategy(int code, String type) { + this.code = code; + this.type = type; + } + + public static LwM2MClientStrategy fromStrategyClientByType(String type) { + for (LwM2MClientStrategy to : LwM2MClientStrategy.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported Client Strategy type : %s", type)); + } + + public static LwM2MClientStrategy fromStrategyClientByCode(int code) { + for (LwM2MClientStrategy to : LwM2MClientStrategy.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported Client Strategy code : %s", code)); + } + } + + public enum LwM2MFirmwareUpdateStrategy { + OBJ_5_BINARY(1, "ObjectId 5, Binary"), + OBJ_5_TEMP_URL(2, "ObjectId 5, URI"), + OBJ_19_BINARY(3, "ObjectId 19, Binary"); + + public int code; + public String type; + + LwM2MFirmwareUpdateStrategy(int code, String type) { + this.code = code; + this.type = type; + } + + public static LwM2MFirmwareUpdateStrategy fromStrategyFwByType(String type) { + for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type)); + } + + public static LwM2MFirmwareUpdateStrategy fromStrategyFwByCode(int code) { + for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported FW Strategy code : %s", code)); + } + } + + public enum LwM2MSoftwareUpdateStrategy { + BINARY(1, "ObjectId 9, Binary"), + TEMP_URL(2, "ObjectId 9, URI"); + + public int code; + public String type; + + LwM2MSoftwareUpdateStrategy(int code, String type) { + this.code = code; + this.type = type; + } + + public static LwM2MSoftwareUpdateStrategy fromStrategySwByType(String type) { + for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW Strategy type : %s", type)); + } + + public static LwM2MSoftwareUpdateStrategy fromStrategySwByCode(int code) { + for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW Strategy code : %s", code)); + } + + } + /** * FirmwareUpdateStatus { * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED */ - public static FirmwareUpdateStatus EqualsSwSateToFirmwareUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) { + public static OtaPackageUpdateStatus EqualsSwSateToFirmwareUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) { switch (updateResultSw) { case INITIAL: switch (updateStateSw) { @@ -533,10 +675,6 @@ public class LwM2mTransportUtil { } } - public static final String EVENT_AWAKE = "AWAKE"; - public static final String RESPONSE_REQUEST_CHANNEL = "RESP_REQ"; - public static final String RESPONSE_CHANNEL = "RESP"; - public static final String OBSERVE_CHANNEL = "OBSERVE"; public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException { @@ -557,6 +695,36 @@ public class LwM2mTransportUtil { } } + public static LwM2mOtaConvert convertOtaUpdateValueToString (String pathIdVer, Object value, ResourceModel.Type currentType) { + String path = convertPathFromIdVerToObjectId(pathIdVer); + LwM2mOtaConvert lwM2mOtaConvert = new LwM2mOtaConvert(); + if (path != null) { + if (FW_STATE_ID.equals(path)) { + lwM2mOtaConvert.setCurrentType(STRING); + lwM2mOtaConvert.setValue(StateFw.fromStateFwByCode(((Long) value).intValue()).type); + return lwM2mOtaConvert; + } + else if (FW_RESULT_ID.equals(path)) { + lwM2mOtaConvert.setCurrentType(STRING); + lwM2mOtaConvert.setValue(UpdateResultFw.fromUpdateResultFwByCode(((Long) value).intValue()).type); + return lwM2mOtaConvert; + } + else if (SW_UPDATE_STATE_ID.equals(path)) { + lwM2mOtaConvert.setCurrentType(STRING); + lwM2mOtaConvert.setValue(UpdateStateSw.fromUpdateStateSwByCode(((Long) value).intValue()).type); + return lwM2mOtaConvert; + } + else if (SW_RESULT_ID.equals(path)) { + lwM2mOtaConvert.setCurrentType(STRING); + lwM2mOtaConvert.setValue(UpdateResultSw.fromUpdateResultSwByCode(((Long) value).intValue()).type); + return lwM2mOtaConvert; + } + } + lwM2mOtaConvert.setCurrentType(currentType); + lwM2mOtaConvert.setValue(value); + return lwM2mOtaConvert; + } + public static LwM2mNode getLvM2mNodeToObject(LwM2mNode content) { if (content instanceof LwM2mObject) { return (LwM2mObject) content; @@ -632,11 +800,6 @@ public class LwM2mTransportUtil { return null; } - public static int getClientOnlyObserveAfterConnect(LwM2mClientProfile profile) { - return profile.getPostClientLwM2mSettings().getAsJsonObject().has("clientOnlyObserveAfterConnect") ? - profile.getPostClientLwM2mSettings().getAsJsonObject().get("clientOnlyObserveAfterConnect").getAsInt() : 1; - } - private static boolean getValidateCredentialsBodyFromThingsboard(JsonObject objectMsg) { return (objectMsg != null && !objectMsg.isJsonNull() && @@ -838,7 +1001,7 @@ public class LwM2mTransportUtil { * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); * Attribute [] attrs = {gt, st}; */ - public static DownlinkRequest createWriteAttributeRequest(String target, Object params, DefaultLwM2MTransportMsgHandler serviceImpl) { + public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, DefaultLwM2MTransportMsgHandler serviceImpl) { AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target)); return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; } @@ -913,7 +1076,7 @@ public class LwM2mTransportUtil { case GREATER_THAN: case LESSER_THAN: case STEP: - if (value.getClass().getSimpleName().equals("String") ) { + if (value.getClass().getSimpleName().equals("String")) { value = Double.valueOf((String) value); } return serviceImpl.converter.convertValue(value, equalsResourceTypeGetSimpleName(value), FLOAT, new LwM2mPath(target)); @@ -927,20 +1090,20 @@ public class LwM2mTransportUtil { return new Attribute(key, attrValue); } catch (Exception e) { log.error("CreateAttribute, not valid parameter key: [{}], attrValue: [{}], error: [{}]", key, attrValue, e.getMessage()); - return null; + return null; } } - public static boolean isFwSwWords (String pathName) { - return FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.TITLE).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.CHECKSUM).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.CHECKSUM_ALGORITHM).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.SIZE).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.VERSION).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.TITLE).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.CHECKSUM).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.CHECKSUM_ALGORITHM).equals(pathName) - || FirmwareUtil.getAttributeKey(FirmwareType.SOFTWARE, FirmwareKey.SIZE).equals(pathName); + public static boolean isFwSwWords(String pathName) { + return OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.SIZE).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) + || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.SIZE).equals(pathName); } } diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientState.java similarity index 82% rename from ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientState.java index 1c55a86d8b..b5ff57afc4 100644 --- a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientState.java @@ -13,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -:host ::ng-deep { - textarea.mat-input-element.cdk-textarea-autosize { - box-sizing: content-box; - } +package org.thingsboard.server.transport.lwm2m.server.client; + +public enum LwM2MClientState { + + CREATED, REGISTERED, UNREGISTERED + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientStateException.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientStateException.java new file mode 100644 index 0000000000..68e3c68742 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2MClientStateException.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.client; + +import lombok.Getter; + +public class LwM2MClientStateException extends Exception { + + private static final long serialVersionUID = 3307690997951364046L; + + @Getter + private final LwM2MClientState state; + + public LwM2MClientStateException(LwM2MClientState state, String message) { + super(message); + this.state = state; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 9ef45c3b3b..78f9ffb349 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -26,12 +26,13 @@ import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; +import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; @@ -49,6 +50,8 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; @@ -62,12 +65,28 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.g @Slf4j public class LwM2mClient implements Cloneable { + + private final String nodeId; + @Getter + private final String endpoint; + private final Lock lock; + @Getter + @Setter + private LwM2MClientState state; + @Getter + private final Map resources; + @Getter + private final Map delayedRequests; + @Getter + private final List pendingReadRequests; + @Getter + private final Queue queuedRequests; + @Getter private String deviceName; @Getter private String deviceProfileName; - @Getter - private String endpoint; + @Getter private String identity; @Getter @@ -75,8 +94,6 @@ public class LwM2mClient implements Cloneable { @Getter private UUID deviceId; @Getter - private UUID sessionId; - @Getter private SessionInfoProto session; @Getter private UUID profileId; @@ -92,15 +109,6 @@ public class LwM2mClient implements Cloneable { private ValidateDeviceCredentialsResponse credentials; - @Getter - private final Map resources; - @Getter - private final Map delayedRequests; - @Getter - @Setter - private final List pendingReadRequests; - @Getter - private final Queue queuedRequests; @Getter private boolean init; @@ -108,20 +116,23 @@ public class LwM2mClient implements Cloneable { return super.clone(); } - public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) { + public LwM2mClient(String nodeId, String endpoint) { + this.nodeId = nodeId; this.endpoint = endpoint; - this.identity = identity; - this.securityInfo = securityInfo; - this.credentials = credentials; + this.lock = new ReentrantLock(); this.delayedRequests = new ConcurrentHashMap<>(); this.pendingReadRequests = new CopyOnWriteArrayList<>(); this.resources = new ConcurrentHashMap<>(); - this.profileId = profileId; - this.init = false; this.queuedRequests = new ConcurrentLinkedQueue<>(); + this.state = LwM2MClientState.CREATED; + } - this.fwUpdate = new LwM2mFwSwUpdate(this, FirmwareType.FIRMWARE); - this.swUpdate = new LwM2mFwSwUpdate(this, FirmwareType.SOFTWARE); + public void init(String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) { + this.identity = identity; + this.securityInfo = securityInfo; + this.credentials = credentials; + this.profileId = profileId; + this.init = false; if (this.credentials != null && this.credentials.hasDeviceInfo()) { this.session = createSession(nodeId, sessionId, credentials); this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB()); @@ -131,6 +142,14 @@ public class LwM2mClient implements Cloneable { } } + public void lock() { + lock.lock(); + } + + public void unlock() { + lock.unlock(); + } + public void onDeviceUpdate(Device device, Optional deviceProfileOpt) { SessionInfoProto.Builder builder = SessionInfoProto.newBuilder().mergeFrom(session); this.deviceId = device.getUuidId(); @@ -193,9 +212,7 @@ public class LwM2mClient implements Cloneable { public Object getResourceValue(String pathRezIdVer, String pathRezId) { String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer; if (this.resources.get(pathRez) != null) { - return this.resources.get(pathRez).getLwM2mResource().isMultiInstances() ? - this.resources.get(pathRez).getLwM2mResource().getValues() : - this.resources.get(pathRez).getLwM2mResource().getValue(); + return this.resources.get(pathRez).getLwM2mResource().getValue(); } return null; } @@ -366,5 +383,31 @@ public class LwM2mClient implements Cloneable { } } + public ContentFormat getDefaultContentFormat() { + if (registration == null) { + return ContentFormat.DEFAULT; + } else if (registration.getLwM2mVersion().equals("1.0")) { + return ContentFormat.TLV; + } else { + return ContentFormat.TEXT; + } + } + + public LwM2mFwSwUpdate getFwUpdate (LwM2mClientContext clientContext) { + if (this.fwUpdate == null) { + LwM2mClientProfile lwM2mClientProfile = clientContext.getProfile(this.getProfileId()); + this.fwUpdate = new LwM2mFwSwUpdate(this, OtaPackageType.FIRMWARE, lwM2mClientProfile.getFwUpdateStrategy()); + } + return this.fwUpdate; + } + + public LwM2mFwSwUpdate getSwUpdate (LwM2mClientContext clientContext) { + if (this.swUpdate == null) { + LwM2mClientProfile lwM2mClientProfile = clientContext.getProfile(this.getProfileId()); + this.swUpdate = new LwM2mFwSwUpdate(this, OtaPackageType.SOFTWARE, lwM2mClientProfile.getSwUpdateStrategy()); + } + return this.fwUpdate; + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index bb3e97b7e4..613a8b7971 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java @@ -27,21 +27,17 @@ import java.util.UUID; public interface LwM2mClientContext { - void removeClientByRegistrationId(String registrationId); - - LwM2mClient getClientByEndpoint(String endpoint); - LwM2mClient getClientByRegistrationId(String registrationId); - LwM2mClient getClient(TransportProtos.SessionInfoProto sessionInfo); + LwM2mClient getClientByEndpoint(String endpoint); - LwM2mClient getOrRegister(Registration registration); + LwM2mClient getClientBySessionInfo(TransportProtos.SessionInfoProto sessionInfo); - LwM2mClient registerOrUpdate(Registration registration); + void register(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException; - LwM2mClient fetchClientByEndpoint(String endpoint); + void updateRegistration(LwM2mClient client, Registration registration) throws LwM2MClientStateException; - Registration getRegistration(String registrationId); + void unregister(LwM2mClient client, Registration registration) throws LwM2MClientStateException; Collection getLwM2mClients(); @@ -55,9 +51,11 @@ public interface LwM2mClientContext { LwM2mClientProfile profileUpdate(DeviceProfile deviceProfile); - Set getSupportedIdVerInClient(Registration registration); + Set getSupportedIdVerInClient(LwM2mClient registration); LwM2mClient getClientByDeviceId(UUID deviceId); void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); + + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index fa43909498..02d3425ede 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -19,16 +19,15 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; -import org.eclipse.leshan.server.security.EditableSecurityStore; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; -import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; -import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; import java.util.Arrays; import java.util.Collection; @@ -37,6 +36,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import static org.eclipse.leshan.core.SecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @@ -48,102 +48,122 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c public class LwM2mClientContextImpl implements LwM2mClientContext { private final LwM2mTransportContext context; + private final TbEditableSecurityStore securityStore; private final Map lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); private final Map lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); private Map profiles = new ConcurrentHashMap<>(); - private final LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator; - - private final EditableSecurityStore securityStore; - @Override public LwM2mClient getClientByEndpoint(String endpoint) { - return lwM2mClientsByEndpoint.get(endpoint); + return lwM2mClientsByEndpoint.computeIfAbsent(endpoint, ep -> new LwM2mClient(context.getNodeId(), ep)); } @Override - public LwM2mClient getClientByRegistrationId(String registrationId) { - return lwM2mClientsByRegistrationId.get(registrationId); + public void register(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException { + lwM2MClient.lock(); + try { + if (LwM2MClientState.UNREGISTERED.equals(lwM2MClient.getState())) { + throw new LwM2MClientStateException(lwM2MClient.getState(), "Client is in invalid state."); + } + TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint()); + if (securityInfo.getSecurityMode() != null) { + if (securityInfo.getDeviceProfile() != null) { + UUID profileUuid = profileUpdate(securityInfo.getDeviceProfile()) != null ? securityInfo.getDeviceProfile().getUuidId() : null; + if (securityInfo.getSecurityInfo() != null) { + lwM2MClient.init(securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(), securityInfo.getMsg(), profileUuid, UUID.randomUUID()); + } else if (NO_SEC.equals(securityInfo.getSecurityMode())) { + lwM2MClient.init(null, null, securityInfo.getMsg(), profileUuid, UUID.randomUUID()); + } else { + throw new RuntimeException(String.format("Registration failed: device %s not found.", lwM2MClient.getEndpoint())); + } + } else { + throw new RuntimeException(String.format("Registration failed: device %s not found.", lwM2MClient.getEndpoint())); + } + } else { + throw new RuntimeException(String.format("Registration failed: FORBIDDEN, endpointId: %s", lwM2MClient.getEndpoint())); + } + lwM2MClient.setRegistration(registration); + this.lwM2mClientsByRegistrationId.put(registration.getId(), lwM2MClient); + lwM2MClient.setState(LwM2MClientState.REGISTERED); + } finally { + lwM2MClient.unlock(); + } } @Override - public LwM2mClient getOrRegister(Registration registration) { - if (registration == null) { - return null; - } - LwM2mClient client = lwM2mClientsByRegistrationId.get(registration.getId()); - if (client == null) { - client = lwM2mClientsByEndpoint.get(registration.getEndpoint()); - if (client == null) { - client = registerOrUpdate(registration); + public void updateRegistration(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException { + lwM2MClient.lock(); + try { + if (!LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) { + throw new LwM2MClientStateException(lwM2MClient.getState(), "Client is in invalid state."); } + lwM2MClient.setRegistration(registration); + } finally { + lwM2MClient.unlock(); } - return client; } @Override - public LwM2mClient getClient(TransportProtos.SessionInfoProto sessionInfo) { - return lwM2mClientsByEndpoint.values().stream().filter(c -> - (new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())) - .equals((new UUID(c.getSession().getSessionIdMSB(), c.getSession().getSessionIdLSB()))) + public void unregister(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException { + lwM2MClient.lock(); + try { + if (!LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) { + throw new LwM2MClientStateException(lwM2MClient.getState(), "Client is in invalid state."); + } + lwM2mClientsByRegistrationId.remove(registration.getId()); + Registration currentRegistration = lwM2MClient.getRegistration(); + if (currentRegistration.getId().equals(registration.getId())) { + lwM2MClient.setState(LwM2MClientState.UNREGISTERED); + lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint()); + this.securityStore.remove(lwM2MClient.getEndpoint()); + UUID profileId = lwM2MClient.getProfileId(); + if (profileId != null) { + Optional otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst(); + if (otherClients.isEmpty()) { + profiles.remove(profileId); + } + } + } else { + throw new LwM2MClientStateException(lwM2MClient.getState(), "Client has different registration."); + } + } finally { + lwM2MClient.unlock(); + } + } - ).findAny().get(); + @Override + public LwM2mClient getClientByRegistrationId(String registrationId) { + return lwM2mClientsByRegistrationId.get(registrationId); } @Override - public LwM2mClient registerOrUpdate(Registration registration) { - LwM2mClient lwM2MClient = lwM2mClientsByEndpoint.get(registration.getEndpoint()); - if (lwM2MClient == null) { - lwM2MClient = this.fetchClientByEndpoint(registration.getEndpoint()); + public LwM2mClient getClientBySessionInfo(TransportProtos.SessionInfoProto sessionInfo) { + LwM2mClient lwM2mClient = null; + Predicate isClientFilter = c -> + (new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())) + .equals((new UUID(c.getSession().getSessionIdMSB(), c.getSession().getSessionIdLSB()))); + if (this.lwM2mClientsByEndpoint.size() > 0) { + lwM2mClient = this.lwM2mClientsByEndpoint.values().stream().filter(isClientFilter).findAny().orElse(null); + } + if (lwM2mClient == null && this.lwM2mClientsByRegistrationId.size() > 0) { + lwM2mClient = this.lwM2mClientsByRegistrationId.values().stream().filter(isClientFilter).findAny().orElse(null); } - lwM2MClient.setRegistration(registration); -// TODO: this remove is probably redundant. We should remove it. -// this.lwM2mClientsByEndpoint.remove(registration.getEndpoint()); - this.lwM2mClientsByRegistrationId.put(registration.getId(), lwM2MClient); - return lwM2MClient; + if (lwM2mClient == null) { + log.warn("Device TimeOut? lwM2mClient is null."); + log.warn("SessionInfo input [{}], lwM2mClientsByEndpoint size: [{}] lwM2mClientsByRegistrationId: [{}]", sessionInfo, lwM2mClientsByEndpoint.values(), lwM2mClientsByRegistrationId.values()); + log.error("", new RuntimeException()); + } + return lwM2mClient; } public Registration getRegistration(String registrationId) { return this.lwM2mClientsByRegistrationId.get(registrationId).getRegistration(); } - @Override - public LwM2mClient fetchClientByEndpoint(String endpoint) { - EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); - if (securityInfo.getSecurityMode() != null) { - if (securityInfo.getDeviceProfile() != null) { - UUID profileUuid = profileUpdate(securityInfo.getDeviceProfile())!= null ? - securityInfo.getDeviceProfile().getUuidId() : null; - // TODO: for tests bug. - if (profileUuid== null) { - log.trace("input parameters toClientProfile if the result is null: [{}]", securityInfo.getDeviceProfile()); - } - LwM2mClient client; - if (securityInfo.getSecurityInfo() != null) { - client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(), - securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(), - securityInfo.getMsg(), profileUuid, UUID.randomUUID()); - } else if (NO_SEC.equals(securityInfo.getSecurityMode())) { - client = new LwM2mClient(context.getNodeId(), endpoint, - null, null, - securityInfo.getMsg(), profileUuid, UUID.randomUUID()); - } else { - throw new RuntimeException(String.format("Registration failed: device %s not found.", endpoint)); - } - lwM2mClientsByEndpoint.put(client.getEndpoint(), client); - return client; - } else { - throw new RuntimeException(String.format("Registration failed: device %s not found.", endpoint)); - } - } else { - throw new RuntimeException(String.format("Registration failed: FORBIDDEN, endpointId: %s", endpoint)); - } - } - @Override public void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials) { - LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); - lwM2mClientsByEndpoint.put(registration.getEndpoint(), client); + LwM2mClient client = getClientByEndpoint(registration.getEndpoint()); + client.init(null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); lwM2mClientsByRegistrationId.put(registration.getId(), client); profileUpdate(credentials.getDeviceProfile()); } @@ -165,7 +185,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public LwM2mClientProfile getProfile(Registration registration) { - return this.getProfiles().get(getOrRegister(registration).getProfileId()); + return this.getProfiles().get(getClientByEndpoint(registration.getEndpoint()).getProfileId()); } @Override @@ -180,25 +200,18 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { if (lwM2MClientProfile != null) { profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); return lwM2MClientProfile; - } - else { + } else { return null; } } - /** - * if isVer - ok or default ver=DEFAULT_LWM2M_VERSION - * - * @param registration - - * @return - all objectIdVer in client - */ @Override - public Set getSupportedIdVerInClient(Registration registration) { + public Set getSupportedIdVerInClient(LwM2mClient client) { Set clientObjects = ConcurrentHashMap.newKeySet(); - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); + Arrays.stream(client.getRegistration().getObjectLinks()).forEach(link -> { + LwM2mPath pathIds = new LwM2mPath(link.getUrl()); if (!pathIds.isRoot()) { - clientObjects.add(convertPathFromObjectIdToIdVer(url.getUrl(), registration)); + clientObjects.add(convertPathFromObjectIdToIdVer(link.getUrl(), client.getRegistration())); } }); return (clientObjects.size() > 0) ? clientObjects : null; @@ -209,20 +222,4 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { return lwM2mClientsByRegistrationId.values().stream().filter(e -> deviceId.equals(e.getDeviceId())).findFirst().orElse(null); } - @Override - public void removeClientByRegistrationId(String registrationId) { - LwM2mClient lwM2MClient = this.lwM2mClientsByRegistrationId.get(registrationId); - if (lwM2MClient != null) { - this.securityStore.remove(lwM2MClient.getEndpoint(), false); - this.lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint()); - this.lwM2mClientsByRegistrationId.remove(registrationId); - UUID profileId = lwM2MClient.getProfileId(); - if (profileId != null) { - Optional otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst(); - if (otherClients.isEmpty()) { - profiles.remove(profileId); - } - } - } - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java index 19af453f3c..67edbf2747 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java @@ -20,15 +20,23 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import lombok.Data; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MClientStrategy; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MSoftwareUpdateStrategy; @Data public class LwM2mClientProfile { + private final String clientStrategyStr = "clientStrategy"; + private final String fwUpdateStrategyStr = "fwUpdateStrategy"; + private final String swUpdateStrategyStr = "swUpdateStrategy"; private TenantId tenantId; /** - * {"clientLwM2mSettings": { - * clientUpdateValueAfterConnect: false; - * } + * "clientLwM2mSettings": { + * "fwUpdateStrategy": "1", + * "swUpdateStrategy": "1", + * "clientStrategy": "1" + * } **/ private JsonObject postClientLwM2mSettings; @@ -85,5 +93,21 @@ public class LwM2mClientProfile { } } + public int getClientStrategy() { + return this.postClientLwM2mSettings.getAsJsonObject().has(this.clientStrategyStr) ? + Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.clientStrategyStr).getAsString()) : + LwM2MClientStrategy.CLIENT_STRATEGY_1.code; + } + public int getFwUpdateStrategy() { + return this.postClientLwM2mSettings.getAsJsonObject().has(this.fwUpdateStrategyStr) ? + Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.fwUpdateStrategyStr).getAsString()) : + LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code; + } + + public int getSwUpdateStrategy() { + return this.postClientLwM2mSettings.getAsJsonObject().has(this.swUpdateStrategyStr) ? + Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.swUpdateStrategyStr).getAsString()) : + LwM2MSoftwareUpdateStrategy.BINARY.code; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientRpcRequest.java similarity index 97% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientRpcRequest.java index b31f841065..33525426e3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientRpcRequest.java @@ -39,6 +39,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.K import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.FW_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; @@ -56,7 +57,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.v @Slf4j @Data -public class Lwm2mClientRpcRequest { +public class LwM2mClientRpcRequest { private Registration registration; private TransportProtos.SessionInfoProto sessionInfo; @@ -74,10 +75,10 @@ public class Lwm2mClientRpcRequest { private String infoMsg; private String responseCode; - public Lwm2mClientRpcRequest() { + public LwM2mClientRpcRequest() { } - public Lwm2mClientRpcRequest(LwM2mTypeOper lwM2mTypeOper, String bodyParams, int requestId, + public LwM2mClientRpcRequest(LwM2mTypeOper lwM2mTypeOper, String bodyParams, int requestId, TransportProtos.SessionInfoProto sessionInfo, Registration registration, DefaultLwM2MTransportMsgHandler handler) { this.registration = registration; this.sessionInfo = sessionInfo; @@ -140,7 +141,8 @@ public class Lwm2mClientRpcRequest { if (this.getTargetIdVer() == null && !(OBSERVE_READ_ALL == this.getTypeOper() || DISCOVER_ALL == this.getTypeOper() - || OBSERVE_CANCEL == this.getTypeOper())) { + || OBSERVE_CANCEL == this.getTypeOper() + || FW_UPDATE == this.getTypeOper())) { this.setErrorMsg(TARGET_ID_VER_KEY + " and " + KEY_NAME_KEY + " is null or bad format"); } @@ -271,7 +273,7 @@ public class Lwm2mClientRpcRequest { } private String getRezIdByResourceNameAndObjectInstanceId(String resourceName, DefaultLwM2MTransportMsgHandler handler) { - LwM2mClient lwM2mClient = handler.clientContext.getClient(this.sessionInfo); + LwM2mClient lwM2mClient = handler.clientContext.getClientBySessionInfo(this.sessionInfo); return lwM2mClient != null ? lwM2mClient.getRezIdByResourceNameAndObjectInstanceId(resourceName, this.targetIdVer, handler.config.getModelProvider()) : null; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java index e3e8608839..6828761fb0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java @@ -20,8 +20,9 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.request.ContentFormat; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus; +import org.eclipse.leshan.server.registration.Registration; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportRequest; @@ -32,20 +33,32 @@ import java.util.List; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; -import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE; -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; -import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE; -import static org.thingsboard.server.common.data.firmware.FirmwareUpdateStatus.UPDATING; -import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey; +import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; +import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; +import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_19_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_URI_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; @@ -58,6 +71,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.S import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsFwSateToFirmwareUpdateStatus; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.splitCamelCaseString; @Slf4j @@ -97,28 +111,37 @@ public class LwM2mFwSwUpdate { @Getter @Setter private volatile boolean infoFwSwUpdate = false; - private final FirmwareType type; + private final OtaPackageType type; @Getter LwM2mClient lwM2MClient; @Getter @Setter private final List pendingInfoRequestsStart; + @Getter + @Setter + private volatile LwM2mClientRpcRequest rpcRequest; + @Getter + @Setter + private volatile int updateStrategy; - public LwM2mFwSwUpdate(LwM2mClient lwM2MClient, FirmwareType type) { + public LwM2mFwSwUpdate(LwM2mClient lwM2MClient, OtaPackageType type, int updateStrategy) { this.lwM2MClient = lwM2MClient; this.pendingInfoRequestsStart = new CopyOnWriteArrayList<>(); this.type = type; this.stateUpdate = null; + this.updateStrategy = updateStrategy; this.initPathId(); } private void initPathId() { if (FIRMWARE.equals(this.type)) { - this.pathPackageId = FW_PACKAGE_ID; + this.pathPackageId = LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy ? + FW_PACKAGE_5_ID : LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy ? + FW_PACKAGE_URI_ID : FW_PACKAGE_19_ID; this.pathStateId = FW_STATE_ID; this.pathResultId = FW_RESULT_ID; this.pathNameId = FW_NAME_ID; - this.pathVerId = FW_VER_ID; + this.pathVerId = FW_5_VER_ID; this.pathInstallId = FW_UPDATE_ID; this.wUpdate = FW_UPDATE; } else if (SOFTWARE.equals(this.type)) { @@ -139,13 +162,13 @@ public class LwM2mFwSwUpdate { } if (this.pendingInfoRequestsStart.size() == 0) { this.infoFwSwUpdate = false; - if (!FirmwareUpdateStatus.DOWNLOADING.name().equals(this.stateUpdate)) { - boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart() : - this.conditionalSwUpdateStart(); - if (conditionalStart) { - this.writeFwSwWare(handler, request); - } +// if (!FAILED.name().equals(this.stateUpdate)) { + boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart(handler) : + this.conditionalSwUpdateStart(handler); + if (conditionalStart) { + this.writeFwSwWare(handler, request); } +// } } } @@ -153,26 +176,49 @@ public class LwM2mFwSwUpdate { * Send FsSw to Lwm2mClient: * before operation Write: fw_state = DOWNLOADING */ - private void writeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { - this.stateUpdate = FirmwareUpdateStatus.DOWNLOADING.name(); -// this.observeStateUpdate(); - this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); - int chunkSize = 0; - int chunk = 0; - byte[] firmwareChunk = handler.firmwareDataCache.get(this.currentId.toString(), chunkSize, chunk); - String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration()); - request.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(), - firmwareChunk, handler.config.getTimeout(), null); + public void writeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { + if (this.currentId != null) { + this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name(); + this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); + String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration()); + String fwMsg = String.format("%s: Start type operation %s paths: %s", LOG_LW2M_INFO, + LwM2mTransportUtil.LwM2mTypeOper.FW_UPDATE.name(), this.pathPackageId); + handler.sendLogsToThingsboard(fwMsg, lwM2MClient.getRegistration().getId()); + log.warn("8) Start firmware Update. Send save to: [{}] ver: [{}] path: [{}]", this.lwM2MClient.getDeviceName(), this.currentVersion, targetIdVer); + if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy) { + int chunkSize = 0; + int chunk = 0; + byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk); + request.sendAllRequest(this.lwM2MClient, targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE, + firmwareChunk, handler.config.getTimeout(), this.rpcRequest); + } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { + String apiFont = "coap://176.36.143.9:5685"; + String uri = apiFont + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + this.currentId.toString(); + log.warn("89) coapUri: [{}]", uri); + request.sendAllRequest(this.lwM2MClient, targetIdVer, WRITE_REPLACE, null, + uri, handler.config.getTimeout(), this.rpcRequest); + } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { + + } + } else { + String msgError = "FirmWareId is null."; + log.warn("6) [{}]", msgError); + if (this.rpcRequest != null) { + handler.sentRpcResponse(this.rpcRequest, CONTENT.name(), msgError, LOG_LW2M_ERROR); + } + log.error(msgError); + this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); + } } public void sendLogs(DefaultLwM2MTransportMsgHandler handler, String typeOper, String typeInfo, String msgError) { - this.sendSateOnThingsBoard(handler); +// this.sendSateOnThingsBoard(handler); String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s state - %s.", typeInfo, this.wUpdate, typeOper, this.currentVersion, this.currentTitle, this.stateUpdate); if (LOG_LW2M_ERROR.equals(typeInfo)) { msg = String.format("%s Error: %s", msg, msgError); } - handler.sendLogsToThingsboard(msg, lwM2MClient.getRegistration().getId()); + handler.sendLogsToThingsboard(lwM2MClient, msg); } @@ -182,32 +228,49 @@ public class LwM2mFwSwUpdate { * send execute */ public void executeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { - this.setStateUpdate(UPDATING.name()); this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); - request.sendAllRequest(this.lwM2MClient.getRegistration(), this.pathInstallId, EXECUTE, ContentFormat.TLV.getName(), - null, 0, null); + request.sendAllRequest(this.lwM2MClient, this.pathInstallId, EXECUTE, null, 0, this.rpcRequest); } /** - * Firmware start: + * Firmware start: Check if the version has changed and launch a new update. + * -ObjectId 5, Binary or ObjectId 5, URI * -- If the result of the update - errors (more than 1) - This means that the previous. the update failed. * - We launch the update regardless of the state of the firmware and its version. - * -- If the result of the update is not errors (equal to 1 or 0) and ver is not empty - This means that before the update has passed. - * -- If the result of the update is not errors and is empty - This means that there has not been an update yet. - * - Check if the version has changed and launch a new update. + * -- If the result of the update - errors (more than 1) - This means that the previous. the update failed. + * * ObjectId 5, Binary + * -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed. + * Compare current versions by equals. + * * ObjectId 5, URI + * -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed. + * Compare current versions by contains. */ - private boolean conditionalFwUpdateStart() { + private boolean conditionalFwUpdateStart(DefaultLwM2MTransportMsgHandler handler) { Long updateResultFw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); + String ver5 = (String) this.lwM2MClient.getResourceValue(null, this.pathVerId); + String pathName = (String) this.lwM2MClient.getResourceValue(null, this.pathNameId); + String ver3 = (String) this.lwM2MClient.getResourceValue(null, FW_3_VER_ID); // #1/#2 - return updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code || - ( - (updateResultFw <= LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code - ) && - ( - (this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) || - (this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId))) - ) - ); + String fwMsg = null; + if ((this.currentVersion != null && ( + ver5 != null && ver5.equals(this.currentVersion) || + ver3 != null && ver3.contains(this.currentVersion) + )) || + (this.currentTitle != null && pathName != null && this.currentTitle.equals(pathName))) { + fwMsg = String.format("%s: The update was interrupted. The device has the same version: %s.", LOG_LW2M_ERROR, + this.currentVersion); + } + else if (updateResultFw != null && updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { + fwMsg = String.format("%s: The update was interrupted. The device has the status UpdateResult: error (%d).", LOG_LW2M_ERROR, + updateResultFw); + } + if (fwMsg != null) { + handler.sendLogsToThingsboard(fwMsg, lwM2MClient.getRegistration().getId()); + return false; + } + else { + return true; + } } @@ -231,7 +294,7 @@ public class LwM2mFwSwUpdate { /** * After operation Execute success inspection Update Result : - * > 1 error: "Firmware updated successfully" + * > 1 error: "Firmware updated successfully" */ public boolean conditionalFwExecuteAfterError() { Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); @@ -247,7 +310,7 @@ public class LwM2mFwSwUpdate { * - If Update Result is not errors and ver is not empty - This means that before unInstall update * * - Check if the version has changed and launch a new update. */ - private boolean conditionalSwUpdateStart() { + private boolean conditionalSwUpdateStart(DefaultLwM2MTransportMsgHandler handler) { Long updateResultSw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); // #1/#2 return updateResultSw >= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code || @@ -275,7 +338,7 @@ public class LwM2mFwSwUpdate { * -- inspection Update Result: * ---- FW если Update Result == 1 ("Firmware updated successfully") или SW если Update Result == 2 ("Software successfully installed.") * -- fw_state/sw_state = UPDATED - * + *

* After finish operation Execute (error): * -- inspection updateResult and send to thingsboard info about error * --- send to telemetry ( key - this is name Update Result in model) ( @@ -287,10 +350,10 @@ public class LwM2mFwSwUpdate { LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type; String key = splitCamelCaseString((String) this.lwM2MClient.getResourceNameByRezId(null, this.pathResultId)); if (success) { - this.stateUpdate = FirmwareUpdateStatus.UPDATED.name(); + this.stateUpdate = OtaPackageUpdateStatus.UPDATED.name(); this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); } else { - this.stateUpdate = FirmwareUpdateStatus.FAILED.name(); + this.stateUpdate = OtaPackageUpdateStatus.FAILED.name(); this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_ERROR, value); } handler.helper.sendParametersOnThingsboardTelemetry( @@ -308,7 +371,7 @@ public class LwM2mFwSwUpdate { /** * After operation Execute success inspection Update Result : - * >= 50 - error "NOT_ENOUGH_STORAGE" + * >= 50 - error "NOT_ENOUGH_STORAGE" */ public boolean conditionalSwExecuteAfterError() { Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); @@ -316,10 +379,10 @@ public class LwM2mFwSwUpdate { } private void observeStateUpdate(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { - request.sendAllRequest(lwM2MClient.getRegistration(), + request.sendAllRequest(lwM2MClient, convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE, null, null, 0, null); - request.sendAllRequest(lwM2MClient.getRegistration(), + request.sendAllRequest(lwM2MClient, convertPathFromObjectIdToIdVer(this.pathResultId, this.lwM2MClient.getRegistration()), OBSERVE, null, null, 0, null); } @@ -337,18 +400,75 @@ public class LwM2mFwSwUpdate { public void sendReadObserveInfo(LwM2mTransportRequest request) { this.infoFwSwUpdate = true; - this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( - this.pathVerId, this.lwM2MClient.getRegistration())); - this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( - this.pathNameId, this.lwM2MClient.getRegistration())); this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( this.pathStateId, this.lwM2MClient.getRegistration())); this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( this.pathResultId, this.lwM2MClient.getRegistration())); + this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + FW_3_VER_ID, this.lwM2MClient.getRegistration())); + if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy || + LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy || + SOFTWARE.equals(this.type)) { + this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + this.pathVerId, this.lwM2MClient.getRegistration())); + this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + this.pathNameId, this.lwM2MClient.getRegistration())); + } this.pendingInfoRequestsStart.forEach(pathIdVer -> { - request.sendAllRequest(this.lwM2MClient.getRegistration(), pathIdVer, OBSERVE, ContentFormat.TLV.getName(), - null, 0, null); + request.sendAllRequest(this.lwM2MClient, pathIdVer, OBSERVE, null, 0, this.rpcRequest); }); } + + /** + * Before operation Execute (FwUpdate) inspection Update Result : + * - after finished operation Write result: success (FwUpdate): fw_state = DOWNLOADED + * - before start operation Execute (FwUpdate) Update Result = 0 - Initial value + * - start Execute (FwUpdate) + * After finished operation Execute (FwUpdate) inspection Update Result : + * - after start operation Execute (FwUpdate): fw_state = UPDATING + * - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully") + * - finished operation Execute (FwUpdate) + */ + public void updateStateOta(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request, + Registration registration, String path, int value) { + if (OBJ_5_BINARY.code == this.getUpdateStrategy()) { + if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { + if (DOWNLOADED.name().equals(this.getStateUpdate()) + && this.conditionalFwExecuteStart()) { + this.executeFwSwWare(handler, request); + } else if (UPDATING.name().equals(this.getStateUpdate()) + && this.conditionalFwExecuteAfterSuccess()) { + this.finishFwSwUpdate(handler, true); + } else if (UPDATING.name().equals(this.getStateUpdate()) + && this.conditionalFwExecuteAfterError()) { + this.finishFwSwUpdate(handler, false); + } + } + } else if (OBJ_5_TEMP_URL.code == this.getUpdateStrategy()) { + if (this.currentId != null && (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) { + String state = equalsFwSateToFirmwareUpdateStatus(LwM2mTransportUtil.StateFw.fromStateFwByCode(value)).name(); + if (StringUtils.isNotEmpty(state) && !FAILED.name().equals(this.stateUpdate) && !state.equals(this.stateUpdate)) { + this.stateUpdate = state; + this.sendSateOnThingsBoard(handler); + } + if (value == LwM2mTransportUtil.StateFw.DOWNLOADED.code) { + this.executeFwSwWare(handler, request); + } + handler.firmwareUpdateState.put(lwM2MClient.getEndpoint(), value); + } + if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { + if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.INITIAL.code) { + this.setStateUpdate(INITIATED.name()); + } else if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { + this.setStateUpdate(UPDATED.name()); + } else if (value > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { + this.setStateUpdate(FAILED.name()); + } + this.sendSateOnThingsBoard(handler); + } + } else if (OBJ_19_BINARY.code == this.getUpdateStrategy()) { + + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbEditableSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbEditableSecurityStore.java new file mode 100644 index 0000000000..9efd07dde0 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbEditableSecurityStore.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.store; + +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; + +public interface TbEditableSecurityStore extends TbSecurityStore { + + void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException; + + void remove(String endpoint); + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java new file mode 100644 index 0000000000..9c0b109082 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemorySecurityStore.java @@ -0,0 +1,130 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.store; + +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; +import org.eclipse.leshan.server.security.SecurityInfo; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +public class TbInMemorySecurityStore implements TbEditableSecurityStore { + // lock for the two maps + protected final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + protected final Lock readLock = readWriteLock.readLock(); + protected final Lock writeLock = readWriteLock.writeLock(); + + // by client end-point + protected Map securityByEp = new HashMap<>(); + + // by PSK identity + protected Map securityByIdentity = new HashMap<>(); + + public TbInMemorySecurityStore() { + } + + /** + * {@inheritDoc} + */ + @Override + public SecurityInfo getByEndpoint(String endpoint) { + readLock.lock(); + try { + TbLwM2MSecurityInfo securityInfo = securityByEp.get(endpoint); + if (securityInfo != null) { + return securityInfo.getSecurityInfo(); + } else { + return null; + } + } finally { + readLock.unlock(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public SecurityInfo getByIdentity(String identity) { + readLock.lock(); + try { + TbLwM2MSecurityInfo securityInfo = securityByIdentity.get(identity); + if (securityInfo != null) { + return securityInfo.getSecurityInfo(); + } else { + return null; + } + } finally { + readLock.unlock(); + } + } + + @Override + public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { + writeLock.lock(); + try { + String identity = null; + if (tbSecurityInfo.getSecurityInfo() != null) { + identity = tbSecurityInfo.getSecurityInfo().getIdentity(); + if (identity != null) { + TbLwM2MSecurityInfo infoByIdentity = securityByIdentity.get(identity); + if (infoByIdentity != null && !tbSecurityInfo.getSecurityInfo().getEndpoint().equals(infoByIdentity.getEndpoint())) { + throw new NonUniqueSecurityInfoException("PSK Identity " + identity + " is already used"); + } + securityByIdentity.put(tbSecurityInfo.getSecurityInfo().getIdentity(), tbSecurityInfo); + } + } + + TbLwM2MSecurityInfo previous = securityByEp.put(tbSecurityInfo.getEndpoint(), tbSecurityInfo); + if (previous != null && previous.getSecurityInfo() != null) { + String previousIdentity = previous.getSecurityInfo().getIdentity(); + if (previousIdentity != null && !previousIdentity.equals(identity)) { + securityByIdentity.remove(previousIdentity); + } + } + } finally { + writeLock.unlock(); + } + } + + @Override + public void remove(String endpoint) { + writeLock.lock(); + try { + TbLwM2MSecurityInfo securityInfo = securityByEp.remove(endpoint); + if (securityInfo != null && securityInfo.getSecurityInfo() != null && securityInfo.getSecurityInfo().getIdentity() != null) { + securityByIdentity.remove(securityInfo.getSecurityInfo().getIdentity()); + } + } finally { + writeLock.unlock(); + } + } + + @Override + public TbLwM2MSecurityInfo getTbLwM2MSecurityInfoByEndpoint(String endpoint) { + readLock.lock(); + try { + return securityByEp.get(endpoint); + } finally { + readLock.unlock(); + } + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index 973124b4e9..1493123bc2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -22,11 +22,13 @@ import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.californium.observation.ObserveUtil; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.redis.RedisRegistrationStore; +import org.eclipse.leshan.server.redis.serialization.IdentitySerDes; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; @@ -73,6 +75,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto private static final String REG_EP = "REG:EP:"; // (Endpoint => Registration) private static final String REG_EP_REGID_IDX = "EP:REGID:"; // secondary index key (Registration ID => Endpoint) private static final String REG_EP_ADDR_IDX = "EP:ADDR:"; // secondary index key (Socket Address => Endpoint) + private static final String REG_EP_IDENTITY = "EP:IDENTITY:"; // secondary index key (Identity => Endpoint) private static final String LOCK_EP = "LOCK:EP:"; private static final byte[] OBS_TKN = "OBS:TKN:".getBytes(UTF_8); private static final String OBS_TKNS_REGID_IDX = "TKNS:REGID:"; // secondary index (token list by registration) @@ -155,6 +158,8 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto connection.set(regid_idx, registration.getEndpoint().getBytes(UTF_8)); byte[] addr_idx = toRegAddrKey(registration.getSocketAddress()); connection.set(addr_idx, registration.getEndpoint().getBytes(UTF_8)); + byte[] identity_idx = toRegIdentityKey(registration.getIdentity()); + connection.set(identity_idx, registration.getEndpoint().getBytes(UTF_8)); // Add or update expiration addOrUpdateExpiration(connection, registration); @@ -167,6 +172,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!oldRegistration.getSocketAddress().equals(registration.getSocketAddress())) { removeAddrIndex(connection, oldRegistration); } + if (!oldRegistration.getIdentity().equals(registration.getIdentity())) { + removeIdentityIndex(connection, oldRegistration); + } // remove old observation Collection obsRemoved = unsafeRemoveAllObservations(connection, oldRegistration.getId()); @@ -222,6 +230,9 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto if (!r.getSocketAddress().equals(updatedRegistration.getSocketAddress())) { removeAddrIndex(connection, r); } + if (!r.getIdentity().equals(updatedRegistration.getIdentity())) { + removeIdentityIndex(connection, r); + } return new UpdatedRegistration(r, updatedRegistration); @@ -268,6 +279,22 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } } + @Override + public Registration getRegistrationByIdentity(Identity identity) { + Validate.notNull(identity); + try (var connection = connectionFactory.getConnection()) { + byte[] ep = connection.get(toRegIdentityKey(identity)); + if (ep == null) { + return null; + } + byte[] data = connection.get(toEndpointKey(ep)); + if (data == null) { + return null; + } + return deserializeReg(data); + } + } + @Override public Iterator getAllRegistrations() { try (var connection = connectionFactory.getConnection()) { @@ -325,6 +352,7 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto connection.del(toEndpointKey(r.getEndpoint())); Collection obsRemoved = unsafeRemoveAllObservations(connection, r.getId()); removeAddrIndex(connection, r); + removeIdentityIndex(connection, r); removeExpiration(connection, r); return new Deregistration(r, obsRemoved); } @@ -337,23 +365,31 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto } } - private void removeAddrIndex(RedisConnection connection, Registration registration) { + private void removeAddrIndex(RedisConnection connection, Registration r) { + removeSecondaryIndex(connection, toRegAddrKey(r.getSocketAddress()), r.getEndpoint()); + } + + private void removeIdentityIndex(RedisConnection connection, Registration r) { + removeSecondaryIndex(connection, toRegIdentityKey(r.getIdentity()), r.getEndpoint()); + } + + //TODO: JedisCluster didn't implement Transaction, maybe should use some advanced key creation strategies + private void removeSecondaryIndex(RedisConnection connection, byte[] indexKey, String endpointName) { // Watch the key to remove. - byte[] regAddrKey = toRegAddrKey(registration.getSocketAddress()); - connection.watch(regAddrKey); +// connection.watch(indexKey); - byte[] epFromAddr = connection.get(regAddrKey); + byte[] epFromAddr = connection.get(indexKey); // Delete the key if needed. - if (Arrays.equals(epFromAddr, registration.getEndpoint().getBytes(UTF_8))) { + if (Arrays.equals(epFromAddr, endpointName.getBytes(UTF_8))) { // Try to delete the key - connection.multi(); - connection.del(regAddrKey); - connection.exec(); - // if transaction failed this is not an issue as the socket address is probably reused and we don't neeed to +// connection.multi(); + connection.del(indexKey); +// connection.exec(); + // if transaction failed this is not an issue as the index is probably reused and we don't need to // delete it anymore. } else { // the key must not be deleted. - connection.unwatch(); +// connection.unwatch(); } } @@ -373,6 +409,10 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto return toKey(REG_EP_ADDR_IDX, addr.getAddress().toString() + ":" + addr.getPort()); } + private byte[] toRegIdentityKey(Identity identity) { + return toKey(REG_EP_IDENTITY, IdentitySerDes.serialize(identity).toString()); + } + private byte[] toEndpointKey(String endpoint) { return toKey(REG_EP, endpoint); } @@ -722,7 +762,6 @@ public class TbLwM2mRedisRegistrationStore implements CaliforniumRegistrationSto @Override public void run() { - try (var connection = connectionFactory.getConnection()) { Set endpointsExpired = connection.zRangeByScore(EXP_EP, Double.NEGATIVE_INFINITY, System.currentTimeMillis(), 0, cleanLimit); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index 4cfe2a6829..9e3fe5625d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -24,13 +24,14 @@ import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedList; import java.util.List; -public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { +public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { private static final String SEC_EP = "SEC#EP#"; private static final String PSKID_SEC = "PSKID#SEC"; @@ -72,73 +73,89 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { } @Override - public Collection getAll() { - try (var connection = connectionFactory.getConnection()) { - Collection list = new LinkedList<>(); - ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(SEC_EP + "*").build(); - List> scans = new ArrayList<>(); - if (connection instanceof RedisClusterConnection) { - ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> { - scans.add(((RedisClusterConnection) connection).scan(node, scanOptions)); - }); - } else { - scans.add(connection.scan(scanOptions)); - } - - scans.forEach(scan -> { - scan.forEachRemaining(key -> { - byte[] element = connection.get(key); - list.add(deserialize(element)); - }); - }); - return list; - } + public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { + //TODO: implement } @Override - public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { - byte[] data = serialize(info); - try (var connection = connectionFactory.getConnection()) { - if (info.getIdentity() != null) { - // populate the secondary index (security info by PSK id) - String oldEndpoint = new String(connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes())); - if (!oldEndpoint.equals(info.getEndpoint())) { - throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); - } - connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); - } - - byte[] previousData = connection.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); - SecurityInfo previous = previousData == null ? null : deserialize(previousData); - String previousIdentity = previous == null ? null : previous.getIdentity(); - if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { - connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); - } - - return previous; - } + public TbLwM2MSecurityInfo getTbLwM2MSecurityInfoByEndpoint(String endpoint) { + //TODO: implement + return null; } @Override - public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { - try (var connection = connectionFactory.getConnection()) { - byte[] data = connection.get((SEC_EP + endpoint).getBytes()); - - if (data != null) { - SecurityInfo info = deserialize(data); - if (info.getIdentity() != null) { - connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); - } - connection.del((SEC_EP + endpoint).getBytes()); - if (listener != null) { - listener.securityInfoRemoved(infosAreCompromised, info); - } - return info; - } - } - return null; + public void remove(String endpoint) { + //TODO: implement } + // @Override +// public Collection getAll() { +// try (var connection = connectionFactory.getConnection()) { +// Collection list = new LinkedList<>(); +// ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(SEC_EP + "*").build(); +// List> scans = new ArrayList<>(); +// if (connection instanceof RedisClusterConnection) { +// ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> { +// scans.add(((RedisClusterConnection) connection).scan(node, scanOptions)); +// }); +// } else { +// scans.add(connection.scan(scanOptions)); +// } +// +// scans.forEach(scan -> { +// scan.forEachRemaining(key -> { +// byte[] element = connection.get(key); +// list.add(deserialize(element)); +// }); +// }); +// return list; +// } +// } +// +// @Override +// public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { +// byte[] data = serialize(info); +// try (var connection = connectionFactory.getConnection()) { +// if (info.getIdentity() != null) { +// // populate the secondary index (security info by PSK id) +// String oldEndpoint = new String(connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes())); +// if (!oldEndpoint.equals(info.getEndpoint())) { +// throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); +// } +// connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); +// } +// +// byte[] previousData = connection.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); +// SecurityInfo previous = previousData == null ? null : deserialize(previousData); +// String previousIdentity = previous == null ? null : previous.getIdentity(); +// if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { +// connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); +// } +// +// return previous; +// } +// } +// +// @Override +// public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { +// try (var connection = connectionFactory.getConnection()) { +// byte[] data = connection.get((SEC_EP + endpoint).getBytes()); +// +// if (data != null) { +// SecurityInfo info = deserialize(data); +// if (info.getIdentity() != null) { +// connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); +// } +// connection.del((SEC_EP + endpoint).getBytes()); +// if (listener != null) { +// listener.securityInfoRemoved(infosAreCompromised, info); +// } +// return info; +// } +// } +// return null; +// } + private byte[] serialize(SecurityInfo secInfo) { return SecurityInfoSerDes.serialize(secInfo); } @@ -147,8 +164,4 @@ public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { return SecurityInfoSerDes.deserialize(data); } - @Override - public void setListener(SecurityStoreListener listener) { - this.listener = listener; - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java index 701d629154..cbdb9faf07 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java @@ -19,63 +19,39 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; +import org.eclipse.leshan.server.security.SecurityStore; import org.eclipse.leshan.server.security.SecurityStoreListener; +import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Component; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import java.util.Collection; @Slf4j -@Component -@TbLwM2mTransportComponent -public class TbLwM2mSecurityStore implements EditableSecurityStore { +public class TbLwM2mSecurityStore implements TbEditableSecurityStore { - private final LwM2mClientContext clientContext; - private final EditableSecurityStore securityStore; + private final TbEditableSecurityStore securityStore; + private final LwM2mCredentialsSecurityInfoValidator validator; - public TbLwM2mSecurityStore(LwM2mClientContext clientContext, EditableSecurityStore securityStore) { - this.clientContext = clientContext; + public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) { this.securityStore = securityStore; + this.validator = validator; } @Override - public Collection getAll() { - return securityStore.getAll(); - } - - @Override - public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { - return securityStore.add(info); - } - - @Override - public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { - return securityStore.remove(endpoint, infosAreCompromised); - } - - @Override - public void setListener(SecurityStoreListener listener) { - securityStore.setListener(listener); + public TbLwM2MSecurityInfo getTbLwM2MSecurityInfoByEndpoint(String endpoint) { + return securityStore.getTbLwM2MSecurityInfoByEndpoint(endpoint); } @Override public SecurityInfo getByEndpoint(String endpoint) { SecurityInfo securityInfo = securityStore.getByEndpoint(endpoint); if (securityInfo == null) { - LwM2mClient lwM2mClient = clientContext.getClientByEndpoint(endpoint); - if (lwM2mClient != null && lwM2mClient.getRegistration() != null && !lwM2mClient.getRegistration().getIdentity().isSecure()) { - return null; - } - securityInfo = clientContext.fetchClientByEndpoint(endpoint).getSecurityInfo(); - try { - if (securityInfo != null) { - add(securityInfo); - } - } catch (NonUniqueSecurityInfoException e) { - log.trace("Failed to add security info: {}", securityInfo, e); - } + securityInfo = fetchAndPutSecurityInfo(endpoint); } return securityInfo; } @@ -84,15 +60,31 @@ public class TbLwM2mSecurityStore implements EditableSecurityStore { public SecurityInfo getByIdentity(String pskIdentity) { SecurityInfo securityInfo = securityStore.getByIdentity(pskIdentity); if (securityInfo == null) { - securityInfo = clientContext.fetchClientByEndpoint(pskIdentity).getSecurityInfo(); - try { - if (securityInfo != null) { - add(securityInfo); - } - } catch (NonUniqueSecurityInfoException e) { - log.trace("Failed to add security info: {}", securityInfo, e); - } + securityInfo = fetchAndPutSecurityInfo(pskIdentity); } return securityInfo; } + + @Nullable + public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { + TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); + try { + if (securityInfo != null) { + securityStore.put(securityInfo); + } + } catch (NonUniqueSecurityInfoException e) { + log.trace("Failed to add security info: {}", securityInfo, e); + } + return securityInfo != null ? securityInfo.getSecurityInfo() : null; + } + + @Override + public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { + securityStore.put(tbSecurityInfo); + } + + @Override + public void remove(String endpoint) { + securityStore.remove(endpoint); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java index 2c0c96212f..154de636de 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java @@ -17,17 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; -import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.eclipse.leshan.server.security.InMemorySecurityStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import java.util.Optional; @@ -42,8 +39,7 @@ public class TbLwM2mStoreFactory { private LwM2MTransportServerConfig config; @Autowired - @Lazy - private LwM2mClientContext clientContext; + private LwM2mCredentialsSecurityInfoValidator validator; @Value("${transport.lwm2m.redis.enabled:false}") private boolean useRedis; @@ -55,9 +51,9 @@ public class TbLwM2mStoreFactory { } @Bean - private EditableSecurityStore securityStore() { - return new TbLwM2mSecurityStore(clientContext, redisConfiguration.isPresent() && useRedis ? - new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new InMemorySecurityStore()); + private TbEditableSecurityStore securityStore() { + return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ? + new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new TbInMemorySecurityStore(), validator); } @Bean diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbSecurityStore.java new file mode 100644 index 0000000000..a1aa394fb5 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbSecurityStore.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.server.store; + +import org.eclipse.leshan.server.security.SecurityStore; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; + +public interface TbSecurityStore extends SecurityStore { + + TbLwM2MSecurityInfo getTbLwM2MSecurityInfoByEndpoint(String endpoint); + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index acc6ea1857..9a36b79051 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -43,6 +43,9 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { @Override public Object convertValue(Object value, Type currentType, Type expectedType, LwM2mPath resourcePath) throws CodecException { + if (value == null) { + return null; + } if (expectedType == null) { /** unknown resource, trusted value */ return value; 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 f2a6922b6d..961c20ed34 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 @@ -47,8 +47,8 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.transport.SessionMsgListener; @@ -126,8 +126,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private volatile InetSocketAddress address; private volatile GatewaySessionHandler gatewaySessionHandler; - private final ConcurrentHashMap fwSessions; - private final ConcurrentHashMap fwChunkSizes; + private final ConcurrentHashMap otaPackSessions; + private final ConcurrentHashMap chunkSizes; MqttTransportHandler(MqttTransportContext context, SslHandler sslHandler) { this.sessionId = UUID.randomUUID(); @@ -137,8 +137,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement this.sslHandler = sslHandler; this.mqttQoSMap = new ConcurrentHashMap<>(); this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context); - this.fwSessions = new ConcurrentHashMap<>(); - this.fwChunkSizes = new ConcurrentHashMap<>(); + this.otaPackSessions = new ConcurrentHashMap<>(); + this.chunkSizes = new ConcurrentHashMap<>(); } @Override @@ -320,9 +320,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else if ((fwMatcher = FW_REQUEST_PATTERN.matcher(topicName)).find()) { - getFirmwareCallback(ctx, mqttMsg, msgId, fwMatcher, FirmwareType.FIRMWARE); + getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.FIRMWARE); } else if ((fwMatcher = SW_REQUEST_PATTERN.matcher(topicName)).find()) { - getFirmwareCallback(ctx, mqttMsg, msgId, fwMatcher, FirmwareType.SOFTWARE); + getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.SOFTWARE); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); ack(ctx, msgId); @@ -334,38 +334,38 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void getFirmwareCallback(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, int msgId, Matcher fwMatcher, FirmwareType type) { + private void getOtaPackageCallback(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, int msgId, Matcher fwMatcher, OtaPackageType type) { String payload = mqttMsg.content().toString(UTF8); int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0; String requestId = fwMatcher.group("requestId"); int chunk = Integer.parseInt(fwMatcher.group("chunk")); if (chunkSize > 0) { - this.fwChunkSizes.put(requestId, chunkSize); + this.chunkSizes.put(requestId, chunkSize); } else { - chunkSize = fwChunkSizes.getOrDefault(requestId, 0); + chunkSize = chunkSizes.getOrDefault(requestId, 0); } if (chunkSize > context.getMaxPayloadSize()) { - sendFirmwareError(ctx, PAYLOAD_TOO_LARGE); + sendOtaPackageError(ctx, PAYLOAD_TOO_LARGE); return; } - String firmwareId = fwSessions.get(requestId); + String otaPackageId = otaPackSessions.get(requestId); - if (firmwareId != null) { - sendFirmware(ctx, mqttMsg.variableHeader().packetId(), firmwareId, requestId, chunkSize, chunk, type); + if (otaPackageId != null) { + sendOtaPackage(ctx, mqttMsg.variableHeader().packetId(), otaPackageId, requestId, chunkSize, chunk, type); } else { TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo(); - TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + TransportProtos.GetOtaPackageRequestMsg getOtaPackageRequestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) .setTenantIdMSB(sessionInfo.getTenantIdMSB()) .setTenantIdLSB(sessionInfo.getTenantIdLSB()) .setType(type.name()) .build(); - transportService.process(deviceSessionCtx.getSessionInfo(), getFirmwareRequestMsg, - new FirmwareCallback(ctx, msgId, getFirmwareRequestMsg, requestId, chunkSize, chunk)); + transportService.process(deviceSessionCtx.getSessionInfo(), getOtaPackageRequestMsg, + new OtaPackageCallback(ctx, msgId, getOtaPackageRequestMsg, requestId, chunkSize, chunk)); } } @@ -425,15 +425,15 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private class FirmwareCallback implements TransportServiceCallback { + private class OtaPackageCallback implements TransportServiceCallback { private final ChannelHandlerContext ctx; private final int msgId; - private final TransportProtos.GetFirmwareRequestMsg msg; + private final TransportProtos.GetOtaPackageRequestMsg msg; private final String requestId; private final int chunkSize; private final int chunk; - FirmwareCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetFirmwareRequestMsg msg, String requestId, int chunkSize, int chunk) { + OtaPackageCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetOtaPackageRequestMsg msg, String requestId, int chunkSize, int chunk) { this.ctx = ctx; this.msgId = msgId; this.msg = msg; @@ -443,13 +443,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } @Override - public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) { + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { - FirmwareId firmwareId = new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())); - fwSessions.put(requestId, firmwareId.toString()); - sendFirmware(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk, FirmwareType.valueOf(response.getType())); + OtaPackageId firmwareId = new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())); + otaPackSessions.put(requestId, firmwareId.toString()); + sendOtaPackage(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk, OtaPackageType.valueOf(response.getType())); } else { - sendFirmwareError(ctx, response.getResponseStatus().toString()); + sendOtaPackageError(ctx, response.getResponseStatus().toString()); } } @@ -460,11 +460,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void sendFirmware(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, FirmwareType type) { + private void sendOtaPackage(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, OtaPackageType type) { log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId); ack(ctx, msgId); try { - byte[] firmwareChunk = context.getFirmwareDataCache().get(firmwareId, chunkSize, chunk); + byte[] firmwareChunk = context.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk); deviceSessionCtx.getPayloadAdaptor() .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk, type) .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); @@ -473,7 +473,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void sendFirmwareError(ChannelHandlerContext ctx, String error) { + private void sendOtaPackageError(ChannelHandlerContext ctx, String error) { log.warn("[{}] {}", sessionId, error); deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx .getPayloadAdaptor() 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 a9d7b3e2ea..19d2e15fc5 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 @@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; @@ -155,7 +155,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { } @Override - public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) { + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) { return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index d0c1f30524..62ae6ae027 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -23,7 +23,7 @@ import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttMessageType; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -78,7 +78,7 @@ public interface MqttTransportAdaptor { Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; - Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) throws AdaptorException; + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException; default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) { MqttFixedHeader mqttFixedHeader = 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 08a2f9abe3..a007c73a5a 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 @@ -28,7 +28,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.ProtoConverter; @@ -168,7 +168,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { } @Override - public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) throws AdaptorException { + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, OtaPackageType firmwareType) throws AdaptorException { return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java index 7882ad2410..891c466151 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java @@ -259,8 +259,10 @@ public class GatewaySessionHandler { transportService.process(TransportProtos.TransportToDeviceActorMsg.newBuilder() .setSessionInfo(deviceSessionInfo) .setSessionEvent(DefaultTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN)) - .setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build()) - .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().build()) + .setSubscribeToAttributes(TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC).build()) + .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC).build()) .build(), null); } futureToSet.set(devices.get(deviceName)); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index d4798108c6..d48d3fd04e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -21,14 +21,13 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.cache.firmware.FirmwareDataCache; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; /** * Created by ashvayka on 15.10.18. @@ -53,7 +52,7 @@ public abstract class TransportContext { @Getter @Autowired - private FirmwareDataCache firmwareDataCache; + private OtaPackageDataCache otaPackageDataCache; @Autowired private TransportResourceCache transportResourceCache; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 2209ffc305..4a4e68f64f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -29,8 +29,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOtaPackageRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOtaPackageResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourceResponseMsg; @@ -115,7 +115,7 @@ public interface TransportService { void process(TransportToDeviceActorMsg msg, TransportServiceCallback callback); - void process(SessionInfoProto sessionInfoProto, GetFirmwareRequestMsg msg, TransportServiceCallback callback); + void process(SessionInfoProto sessionInfoProto, GetOtaPackageRequestMsg msg, TransportServiceCallback callback); SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); 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 7035c3374e..77b75dc07a 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 @@ -614,13 +614,13 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetFirmwareRequestMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetOtaPackageRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { TbProtoQueueMsg protoMsg = - new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setFirmwareRequestMsg(msg).build()); + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setOtaPackageRequestMsg(msg).build()); AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), response -> { - callback.onSuccess(response.getValue().getFirmwareResponseMsg()); + callback.onSuccess(response.getValue().getOtaPackageResponseMsg()); }, callback::onError, transportCallbackExecutor); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/Dao.java b/dao/src/main/java/org/thingsboard/server/dao/Dao.java index 0111abdbbe..a5d4dfd9d1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/Dao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/Dao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.TenantId; +import java.util.Collection; import java.util.List; import java.util.UUID; @@ -33,4 +34,6 @@ public interface Dao { boolean removeById(TenantId tenantId, UUID id); + void removeAllByIds(Collection ids); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/TenantEntityWithDataDao.java b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityWithDataDao.java new file mode 100644 index 0000000000..2199a51cd4 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/TenantEntityWithDataDao.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao; + +import org.thingsboard.server.common.data.id.TenantId; + +public interface TenantEntityWithDataDao { + + Long sumDataSizeByTenantId(TenantId tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index eb873db679..3b10f624d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -21,10 +21,12 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; 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; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.dao.Dao; @@ -54,4 +56,7 @@ public interface AlarmDao extends Dao { AlarmDataQuery query, Collection orderedEntityIds); Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set status); + + PageData findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink); + } 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 bb54efe769..90d988c20b 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 @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -119,7 +120,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); if (existing == null || existing.getStatus().isCleared()) { if (!alarmCreationEnabled) { - throw new IllegalStateException("Alarm creation is disabled"); + throw new ApiUsageLimitsExceededException("Alarms creation is disabled"); } return createAlarm(alarm); } else { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 34982b92b6..de13c85727 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; 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.dao.Dao; @@ -81,9 +82,12 @@ public interface DeviceDao extends Dao, TenantEntityDao { */ PageData findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); - PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(UUID tenantId, String type, PageLink pageLink); + PageData findDevicesByTenantIdAndTypeAndEmptyOtaPackage(UUID tenantId, + UUID deviceProfileId, + OtaPackageType type, + PageLink pageLink); - PageData findDevicesByTenantIdAndTypeAndEmptySoftware(UUID tenantId, String type, PageLink pageLink); + Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(UUID tenantId, UUID deviceProfileId, OtaPackageType otaPackageType); /** * Find device infos by tenantId, type and page link. 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 8158134925..7ec1e662af 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 @@ -43,7 +43,7 @@ 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.Firmware; +import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; @@ -57,7 +57,7 @@ import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfilePr 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.firmware.FirmwareType; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -66,7 +66,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; 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.firmware.FirmwareService; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -119,7 +119,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D private CacheManager cacheManager; @Autowired - private FirmwareService firmwareService; + private OtaPackageService otaPackageService; @Autowired private RuleChainService ruleChainService; @@ -427,14 +427,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } if (deviceProfile.getFirmwareId() != null) { - Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId()); + OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getFirmwareId()); if (firmware == null) { throw new DataValidationException("Can't assign non-existent firmware!"); } - if (!firmware.getType().equals(FirmwareType.FIRMWARE)) { + if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) { throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); } - if (firmware.getData() == null) { + if (firmware.getData() == null && !firmware.hasUrl()) { throw new DataValidationException("Can't assign firmware with empty data!"); } if (!firmware.getDeviceProfileId().equals(deviceProfile.getId())) { @@ -443,14 +443,14 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } if (deviceProfile.getSoftwareId() != null) { - Firmware software = firmwareService.findFirmwareById(tenantId, deviceProfile.getSoftwareId()); + OtaPackage software = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getSoftwareId()); if (software == null) { throw new DataValidationException("Can't assign non-existent software!"); } - if (!software.getType().equals(FirmwareType.SOFTWARE)) { + if (!software.getType().equals(OtaPackageType.SOFTWARE)) { throw new DataValidationException("Can't assign software with type: " + software.getType()); } - if (software.getData() == null) { + if (software.getData() == null && !software.hasUrl()) { throw new DataValidationException("Can't assign software with empty data!"); } if (!software.getDeviceProfileId().equals(deviceProfile.getId())) { 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 237d53a7f2..f1470ff5ad 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 @@ -41,7 +41,7 @@ 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.Firmware; +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; @@ -54,13 +54,13 @@ import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfig import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; 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.relation.EntityRelation; @@ -76,7 +76,7 @@ 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.firmware.FirmwareService; +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; @@ -138,7 +138,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe private TbTenantProfileCache tenantProfileCache; @Autowired - private FirmwareService firmwareService; + private OtaPackageService otaPackageService; @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { @@ -201,14 +201,12 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe deviceCredentials.setDeviceId(savedDevice.getId()); if (device.getId() == null) { deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); - } - else { + } else { DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), savedDevice.getId()); if (foundDeviceCredentials == null) { deviceCredentialsService.createDeviceCredentials(savedDevice.getTenantId(), deviceCredentials); - } - else { - deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials); + } else { + deviceCredentialsService.updateDeviceCredentials(device.getTenantId(), deviceCredentials); } } return savedDevice; @@ -364,21 +362,24 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override - public PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink) { - log.trace("Executing findDevicesByTenantIdAndTypeAndEmptyFirmware, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); + public PageData findDevicesByTenantIdAndTypeAndEmptyOtaPackage(TenantId tenantId, + DeviceProfileId deviceProfileId, + OtaPackageType type, + PageLink pageLink) { + log.trace("Executing findDevicesByTenantIdAndTypeAndEmptyOtaPackage, tenantId [{}], deviceProfileId [{}], type [{}], pageLink [{}]", + tenantId, deviceProfileId, type, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateString(type, "Incorrect type " + type); + validateId(tenantId, INCORRECT_DEVICE_PROFILE_ID + deviceProfileId); validatePageLink(pageLink); - return deviceDao.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId.getId(), type, pageLink); + return deviceDao.findDevicesByTenantIdAndTypeAndEmptyOtaPackage(tenantId.getId(), deviceProfileId.getId(), type, pageLink); } @Override - public PageData findDevicesByTenantIdAndTypeAndEmptySoftware(TenantId tenantId, String type, PageLink pageLink) { - log.trace("Executing findDevicesByTenantIdAndTypeAndEmptySoftware, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); + public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type) { + log.trace("Executing countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage, tenantId [{}], deviceProfileId [{}], type [{}]", tenantId, deviceProfileId, type); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validateString(type, "Incorrect type " + type); - validatePageLink(pageLink); - return deviceDao.findDevicesByTenantIdAndTypeAndEmptySoftware(tenantId.getId(), type, pageLink); + validateId(tenantId, INCORRECT_DEVICE_PROFILE_ID + deviceProfileId); + return deviceDao.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(tenantId.getId(), deviceProfileId.getId(), type); } @Override @@ -708,14 +709,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe .ifPresent(DeviceTransportConfiguration::validate); if (device.getFirmwareId() != null) { - Firmware firmware = firmwareService.findFirmwareById(tenantId, device.getFirmwareId()); + OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, device.getFirmwareId()); if (firmware == null) { throw new DataValidationException("Can't assign non-existent firmware!"); } - if (!firmware.getType().equals(FirmwareType.FIRMWARE)) { + if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) { throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); } - if (firmware.getData() == null) { + if (firmware.getData() == null && !firmware.hasUrl()) { throw new DataValidationException("Can't assign firmware with empty data!"); } if (!firmware.getDeviceProfileId().equals(device.getDeviceProfileId())) { @@ -724,14 +725,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } if (device.getSoftwareId() != null) { - Firmware software = firmwareService.findFirmwareById(tenantId, device.getSoftwareId()); + OtaPackage software = otaPackageService.findOtaPackageById(tenantId, device.getSoftwareId()); if (software == null) { throw new DataValidationException("Can't assign non-existent software!"); } - if (!software.getType().equals(FirmwareType.SOFTWARE)) { + if (!software.getType().equals(OtaPackageType.SOFTWARE)) { throw new DataValidationException("Can't assign software with type: " + software.getType()); } - if (software.getData() == null) { + if (software.getData() == null && !software.hasUrl()) { throw new DataValidationException("Can't assign software with empty data!"); } if (!software.getDeviceProfileId().equals(device.getDeviceProfileId())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 73cebc0797..7686d9ec21 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -32,7 +32,7 @@ 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.EntityViewId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; @@ -49,7 +49,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -102,7 +102,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe private ResourceService resourceService; @Autowired - private FirmwareService firmwareService; + private OtaPackageService otaPackageService; @Override public void deleteEntityRelations(TenantId tenantId, EntityId entityId) { @@ -167,8 +167,8 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe case TB_RESOURCE: hasName = resourceService.findResourceInfoByIdAsync(tenantId, new TbResourceId(entityId.getId())); break; - case FIRMWARE: - hasName = firmwareService.findFirmwareInfoByIdAsync(tenantId, new FirmwareId(entityId.getId())); + case OTA_PACKAGE: + hasName = otaPackageService.findOtaPackageInfoByIdAsync(tenantId, new OtaPackageId(entityId.getId())); break; default: throw new IllegalStateException("Not Implemented!"); @@ -192,7 +192,7 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe case DEVICE_PROFILE: case API_USAGE_STATE: case TB_RESOURCE: - case FIRMWARE: + case OTA_PACKAGE: break; case CUSTOMER: hasCustomerId = () -> new CustomerId(entityId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java deleted file mode 100644 index 2e0d5c03b6..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ /dev/null @@ -1,379 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.firmware; - -import com.google.common.hash.HashFunction; -import com.google.common.hash.Hashing; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.hibernate.exception.ConstraintViolationException; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.cache.annotation.Cacheable; -import org.springframework.stereotype.Service; -import org.thingsboard.server.cache.firmware.FirmwareDataCache; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.FirmwareInfo; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; -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.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.TenantDao; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; -import static org.thingsboard.server.dao.service.Validator.validateId; -import static org.thingsboard.server.dao.service.Validator.validatePageLink; - -@Service -@Slf4j -@RequiredArgsConstructor -public class BaseFirmwareService implements FirmwareService { - public static final String INCORRECT_FIRMWARE_ID = "Incorrect firmwareId "; - public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - - private final TenantDao tenantDao; - private final DeviceProfileDao deviceProfileDao; - private final FirmwareDao firmwareDao; - private final FirmwareInfoDao firmwareInfoDao; - private final CacheManager cacheManager; - private final FirmwareDataCache firmwareDataCache; - - @Override - public FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo) { - log.trace("Executing saveFirmwareInfo [{}]", firmwareInfo); - firmwareInfoValidator.validate(firmwareInfo, FirmwareInfo::getTenantId); - try { - FirmwareId firmwareId = firmwareInfo.getId(); - if (firmwareId != null) { - Cache cache = cacheManager.getCache(FIRMWARE_CACHE); - cache.evict(toFirmwareInfoKey(firmwareId)); - firmwareDataCache.evict(firmwareId.toString()); - } - return firmwareInfoDao.save(firmwareInfo.getTenantId(), firmwareInfo); - } catch (Exception t) { - ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); - if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) { - throw new DataValidationException("Firmware with such title and version already exists!"); - } else { - throw t; - } - } - } - - @Override - public Firmware saveFirmware(Firmware firmware) { - log.trace("Executing saveFirmware [{}]", firmware); - firmwareValidator.validate(firmware, FirmwareInfo::getTenantId); - try { - FirmwareId firmwareId = firmware.getId(); - if (firmwareId != null) { - Cache cache = cacheManager.getCache(FIRMWARE_CACHE); - cache.evict(toFirmwareInfoKey(firmwareId)); - firmwareDataCache.evict(firmwareId.toString()); - } - return firmwareDao.save(firmware.getTenantId(), firmware); - } catch (Exception t) { - ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); - if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) { - throw new DataValidationException("Firmware with such title and version already exists!"); - } else { - throw t; - } - } - } - - @Override - public String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) { - if (data == null || !data.hasArray() || data.array().length == 0) { - throw new DataValidationException("Firmware data should be specified!"); - } - - return getHashFunction(checksumAlgorithm).hashBytes(data.array()).toString(); - } - - private HashFunction getHashFunction(ChecksumAlgorithm checksumAlgorithm) { - switch (checksumAlgorithm) { - case MD5: - return Hashing.md5(); - case SHA256: - return Hashing.sha256(); - case SHA384: - return Hashing.sha384(); - case SHA512: - return Hashing.sha512(); - case CRC32: - return Hashing.crc32(); - case MURMUR3_32: - return Hashing.murmur3_32(); - case MURMUR3_128: - return Hashing.murmur3_128(); - default: - throw new DataValidationException("Unknown checksum algorithm!"); - } - } - - @Override - public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { - log.trace("Executing findFirmwareById [{}]", firmwareId); - validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); - return firmwareDao.findById(tenantId, firmwareId.getId()); - } - - @Override - @Cacheable(cacheNames = FIRMWARE_CACHE, key = "{#firmwareId}") - public FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId) { - log.trace("Executing findFirmwareInfoById [{}]", firmwareId); - validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); - return firmwareInfoDao.findById(tenantId, firmwareId.getId()); - } - - @Override - public ListenableFuture findFirmwareInfoByIdAsync(TenantId tenantId, FirmwareId firmwareId) { - log.trace("Executing findFirmwareInfoByIdAsync [{}]", firmwareId); - validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); - return firmwareInfoDao.findByIdAsync(tenantId, firmwareId.getId()); - } - - @Override - public PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink) { - log.trace("Executing findTenantFirmwaresByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validatePageLink(pageLink); - return firmwareInfoDao.findFirmwareInfoByTenantId(tenantId, pageLink); - } - - @Override - public PageData findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink) { - log.trace("Executing findTenantFirmwaresByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - validatePageLink(pageLink); - return firmwareInfoDao.findFirmwareInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, firmwareType, hasData, pageLink); - } - - @Override - public void deleteFirmware(TenantId tenantId, FirmwareId firmwareId) { - log.trace("Executing deleteFirmware [{}]", firmwareId); - validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); - try { - Cache cache = cacheManager.getCache(FIRMWARE_CACHE); - cache.evict(toFirmwareInfoKey(firmwareId)); - firmwareDataCache.evict(firmwareId.toString()); - firmwareDao.removeById(tenantId, firmwareId.getId()); - } catch (Exception t) { - ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); - if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device")) { - throw new DataValidationException("The firmware referenced by the devices cannot be deleted!"); - } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device_profile")) { - throw new DataValidationException("The firmware referenced by the device profile cannot be deleted!"); - } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_software_device")) { - throw new DataValidationException("The software referenced by the devices cannot be deleted!"); - } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_software_device_profile")) { - throw new DataValidationException("The software referenced by the device profile cannot be deleted!"); - } else { - throw t; - } - } - } - - @Override - public void deleteFirmwaresByTenantId(TenantId tenantId) { - log.trace("Executing deleteFirmwaresByTenantId, tenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - tenantFirmwareRemover.removeEntities(tenantId, tenantId); - } - - private DataValidator firmwareInfoValidator = new DataValidator<>() { - - @Override - protected void validateDataImpl(TenantId tenantId, FirmwareInfo firmwareInfo) { - validateImpl(firmwareInfo); - } - - @Override - protected void validateUpdate(TenantId tenantId, FirmwareInfo firmware) { - FirmwareInfo firmwareOld = firmwareInfoDao.findById(tenantId, firmware.getUuidId()); - - validateUpdateDeviceProfile(firmware, firmwareOld); - BaseFirmwareService.validateUpdate(firmware, firmwareOld); - } - }; - - private DataValidator firmwareValidator = new DataValidator<>() { - - @Override - protected void validateDataImpl(TenantId tenantId, Firmware firmware) { - validateImpl(firmware); - - if (StringUtils.isEmpty(firmware.getFileName())) { - throw new DataValidationException("Firmware file name should be specified!"); - } - - if (StringUtils.isEmpty(firmware.getContentType())) { - throw new DataValidationException("Firmware content type should be specified!"); - } - - if (firmware.getChecksumAlgorithm() == null) { - throw new DataValidationException("Firmware checksum algorithm should be specified!"); - } - if (StringUtils.isEmpty(firmware.getChecksum())) { - throw new DataValidationException("Firmware checksum should be specified!"); - } - - String currentChecksum; - - currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData()); - - if (!currentChecksum.equals(firmware.getChecksum())) { - throw new DataValidationException("Wrong firmware file!"); - } - } - - @Override - protected void validateUpdate(TenantId tenantId, Firmware firmware) { - Firmware firmwareOld = firmwareDao.findById(tenantId, firmware.getUuidId()); - - validateUpdateDeviceProfile(firmware, firmwareOld); - BaseFirmwareService.validateUpdate(firmware, firmwareOld); - - if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) { - throw new DataValidationException("Updating firmware data is prohibited!"); - } - } - }; - - private void validateUpdateDeviceProfile(FirmwareInfo firmware, FirmwareInfo firmwareOld) { - if (firmwareOld.getDeviceProfileId() != null && !firmwareOld.getDeviceProfileId().equals(firmware.getDeviceProfileId())) { - if (firmwareInfoDao.isFirmwareUsed(firmwareOld.getId(), firmware.getType(), firmwareOld.getDeviceProfileId())) { - throw new DataValidationException("Can`t update deviceProfileId because firmware is already in use!"); - } - } - } - - private static void validateUpdate(FirmwareInfo firmware, FirmwareInfo firmwareOld) { - if (!firmwareOld.getType().equals(firmware.getType())) { - throw new DataValidationException("Updating type is prohibited!"); - } - - if (!firmwareOld.getTitle().equals(firmware.getTitle())) { - throw new DataValidationException("Updating firmware title is prohibited!"); - } - - if (!firmwareOld.getVersion().equals(firmware.getVersion())) { - throw new DataValidationException("Updating firmware version is prohibited!"); - } - - if (firmwareOld.getFileName() != null && !firmwareOld.getFileName().equals(firmware.getFileName())) { - throw new DataValidationException("Updating firmware file name is prohibited!"); - } - - if (firmwareOld.getContentType() != null && !firmwareOld.getContentType().equals(firmware.getContentType())) { - throw new DataValidationException("Updating firmware content type is prohibited!"); - } - - if (firmwareOld.getChecksumAlgorithm() != null && !firmwareOld.getChecksumAlgorithm().equals(firmware.getChecksumAlgorithm())) { - throw new DataValidationException("Updating firmware content type is prohibited!"); - } - - if (firmwareOld.getChecksum() != null && !firmwareOld.getChecksum().equals(firmware.getChecksum())) { - throw new DataValidationException("Updating firmware content type is prohibited!"); - } - - if (firmwareOld.getDataSize() != null && !firmwareOld.getDataSize().equals(firmware.getDataSize())) { - throw new DataValidationException("Updating firmware data size is prohibited!"); - } - } - - private void validateImpl(FirmwareInfo firmwareInfo) { - if (firmwareInfo.getTenantId() == null) { - throw new DataValidationException("Firmware should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(firmwareInfo.getTenantId(), firmwareInfo.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Firmware is referencing to non-existent tenant!"); - } - } - - if (firmwareInfo.getDeviceProfileId() != null) { - DeviceProfile deviceProfile = deviceProfileDao.findById(firmwareInfo.getTenantId(), firmwareInfo.getDeviceProfileId().getId()); - if (deviceProfile == null) { - throw new DataValidationException("Firmware is referencing to non-existent device profile!"); - } - } - - if (firmwareInfo.getType() == null) { - throw new DataValidationException("Type should be specified!"); - } - - if (StringUtils.isEmpty(firmwareInfo.getTitle())) { - throw new DataValidationException("Firmware title should be specified!"); - } - - if (StringUtils.isEmpty(firmwareInfo.getVersion())) { - throw new DataValidationException("Firmware version should be specified!"); - } - } - - private PaginatedRemover tenantFirmwareRemover = - new PaginatedRemover<>() { - - @Override - protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { - return firmwareInfoDao.findFirmwareInfoByTenantId(id, pageLink); - } - - @Override - protected void removeEntity(TenantId tenantId, FirmwareInfo entity) { - deleteFirmware(tenantId, entity.getId()); - } - }; - - protected Optional extractConstraintViolationException(Exception t) { - if (t instanceof ConstraintViolationException) { - return Optional.of((ConstraintViolationException) t); - } else if (t.getCause() instanceof ConstraintViolationException) { - return Optional.of((ConstraintViolationException) (t.getCause())); - } else { - return Optional.empty(); - } - } - - private static List toFirmwareInfoKey(FirmwareId firmwareId) { - return Collections.singletonList(firmwareId); - } - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 0bc346e36c..02c8db42d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -408,10 +408,20 @@ public class ModelConstants { /** * OAuth2 client registration constants. */ - public static final String OAUTH2_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; + + public static final String OAUTH2_PARAMS_COLUMN_FAMILY_NAME = "oauth2_params"; + public static final String OAUTH2_PARAMS_ENABLED_PROPERTY = "enabled"; + public static final String OAUTH2_PARAMS_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; + + public static final String OAUTH2_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_registration"; + public static final String OAUTH2_DOMAIN_COLUMN_FAMILY_NAME = "oauth2_domain"; + public static final String OAUTH2_MOBILE_COLUMN_FAMILY_NAME = "oauth2_mobile"; + public static final String OAUTH2_PARAMS_ID_PROPERTY = "oauth2_params_id"; + public static final String OAUTH2_PKG_NAME_PROPERTY = "pkg_name"; + public static final String OAUTH2_CALLBACK_URL_SCHEME_PROPERTY = "callback_url_scheme"; + public static final String OAUTH2_CLIENT_REGISTRATION_INFO_COLUMN_FAMILY_NAME = "oauth2_client_registration_info"; public static final String OAUTH2_CLIENT_REGISTRATION_COLUMN_FAMILY_NAME = "oauth2_client_registration"; - public static final String OAUTH2_CLIENT_REGISTRATION_TO_DOMAIN_COLUMN_FAMILY_NAME = "oauth2_client_registration_to_domain"; public static final String OAUTH2_CLIENT_REGISTRATION_TEMPLATE_COLUMN_FAMILY_NAME = "oauth2_client_registration_template"; public static final String OAUTH2_ENABLED_PROPERTY = "enabled"; public static final String OAUTH2_TEMPLATE_PROVIDER_ID_PROPERTY = "provider_id"; @@ -422,7 +432,6 @@ public class ModelConstants { public static final String OAUTH2_CLIENT_SECRET_PROPERTY = "client_secret"; public static final String OAUTH2_AUTHORIZATION_URI_PROPERTY = "authorization_uri"; public static final String OAUTH2_TOKEN_URI_PROPERTY = "token_uri"; - public static final String OAUTH2_REDIRECT_URI_TEMPLATE_PROPERTY = "redirect_uri_template"; public static final String OAUTH2_SCOPE_PROPERTY = "scope"; public static final String OAUTH2_USER_INFO_URI_PROPERTY = "user_info_uri"; public static final String OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY = "user_name_attribute_name"; @@ -479,22 +488,22 @@ public class ModelConstants { public static final String RESOURCE_DATA_COLUMN = "data"; /** - * Firmware constants. - */ - public static final String FIRMWARE_TABLE_NAME = "firmware"; - public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; - public static final String FIRMWARE_DEVICE_PROFILE_ID_COLUMN = "device_profile_id"; - public static final String FIRMWARE_TYPE_COLUMN = "type"; - public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY; - public static final String FIRMWARE_VERSION_COLUMN = "version"; - public static final String FIRMWARE_FILE_NAME_COLUMN = "file_name"; - public static final String FIRMWARE_CONTENT_TYPE_COLUMN = "content_type"; - public static final String FIRMWARE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm"; - public static final String FIRMWARE_CHECKSUM_COLUMN = "checksum"; - public static final String FIRMWARE_DATA_COLUMN = "data"; - public static final String FIRMWARE_DATA_SIZE_COLUMN = "data_size"; - public static final String FIRMWARE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY; - public static final String FIRMWARE_HAS_DATA_PROPERTY = "has_data"; + * Ota Package constants. + */ + public static final String OTA_PACKAGE_TABLE_NAME = "ota_package"; + public static final String OTA_PACKAGE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; + public static final String OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN = "device_profile_id"; + public static final String OTA_PACKAGE_TYPE_COLUMN = "type"; + public static final String OTA_PACKAGE_TILE_COLUMN = TITLE_PROPERTY; + public static final String OTA_PACKAGE_VERSION_COLUMN = "version"; + public static final String OTA_PACKAGE_URL_COLUMN = "url"; + public static final String OTA_PACKAGE_FILE_NAME_COLUMN = "file_name"; + public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type"; + public static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm"; + public static final String OTA_PACKAGE_CHECKSUM_COLUMN = "checksum"; + public static final String OTA_PACKAGE_DATA_COLUMN = "data"; + public static final String OTA_PACKAGE_DATA_SIZE_COLUMN = "data_size"; + public static final String OTA_PACKAGE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY; /** * Edge constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index ed6a871fea..c9913565e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -154,10 +154,10 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti device.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); } if (firmwareId != null) { - device.setFirmwareId(new FirmwareId(firmwareId)); + device.setFirmwareId(new OtaPackageId(firmwareId)); } if (softwareId != null) { - device.setSoftwareId(new FirmwareId(softwareId)); + device.setSoftwareId(new OtaPackageId(softwareId)); } device.setDeviceData(JacksonUtil.convertValue(deviceData, DeviceData.class)); device.setName(name); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index ec440526a4..a3d6c491c0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -178,11 +178,11 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setProvisionDeviceKey(provisionDeviceKey); if (firmwareId != null) { - deviceProfile.setFirmwareId(new FirmwareId(firmwareId)); + deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); } if (softwareId != null) { - deviceProfile.setSoftwareId(new FirmwareId(softwareId)); + deviceProfile.setSoftwareId(new OtaPackageId(softwareId)); } return deviceProfile; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java new file mode 100644 index 0000000000..f1dbe6fff7 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2DomainEntity.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.OAuth2DomainId; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.oauth2.OAuth2Domain; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = ModelConstants.OAUTH2_DOMAIN_COLUMN_FAMILY_NAME) +public class OAuth2DomainEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_PARAMS_ID_PROPERTY) + private UUID oauth2ParamsId; + + @Column(name = ModelConstants.OAUTH2_DOMAIN_NAME_PROPERTY) + private String domainName; + + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_DOMAIN_SCHEME_PROPERTY) + private SchemeType domainScheme; + + public OAuth2DomainEntity() { + super(); + } + + public OAuth2DomainEntity(OAuth2Domain domain) { + if (domain.getId() != null) { + this.setUuid(domain.getId().getId()); + } + this.setCreatedTime(domain.getCreatedTime()); + if (domain.getOauth2ParamsId() != null) { + this.oauth2ParamsId = domain.getOauth2ParamsId().getId(); + } + this.domainName = domain.getDomainName(); + this.domainScheme = domain.getDomainScheme(); + } + + @Override + public OAuth2Domain toData() { + OAuth2Domain domain = new OAuth2Domain(); + domain.setId(new OAuth2DomainId(id)); + domain.setCreatedTime(createdTime); + domain.setOauth2ParamsId(new OAuth2ParamsId(oauth2ParamsId)); + domain.setDomainName(domainName); + domain.setDomainScheme(domainScheme); + return domain; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java new file mode 100644 index 0000000000..403f7af958 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2MobileEntity.java @@ -0,0 +1,72 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.OAuth2MobileId; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = ModelConstants.OAUTH2_MOBILE_COLUMN_FAMILY_NAME) +public class OAuth2MobileEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_PARAMS_ID_PROPERTY) + private UUID oauth2ParamsId; + + @Column(name = ModelConstants.OAUTH2_PKG_NAME_PROPERTY) + private String pkgName; + + @Column(name = ModelConstants.OAUTH2_CALLBACK_URL_SCHEME_PROPERTY) + private String callbackUrlScheme; + + public OAuth2MobileEntity() { + super(); + } + + public OAuth2MobileEntity(OAuth2Mobile mobile) { + if (mobile.getId() != null) { + this.setUuid(mobile.getId().getId()); + } + this.setCreatedTime(mobile.getCreatedTime()); + if (mobile.getOauth2ParamsId() != null) { + this.oauth2ParamsId = mobile.getOauth2ParamsId().getId(); + } + this.pkgName = mobile.getPkgName(); + this.callbackUrlScheme = mobile.getCallbackUrlScheme(); + } + + @Override + public OAuth2Mobile toData() { + OAuth2Mobile mobile = new OAuth2Mobile(); + mobile.setId(new OAuth2MobileId(id)); + mobile.setCreatedTime(createdTime); + mobile.setOauth2ParamsId(new OAuth2ParamsId(oauth2ParamsId)); + mobile.setPkgName(pkgName); + mobile.setCallbackUrlScheme(callbackUrlScheme); + return mobile; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java new file mode 100644 index 0000000000..f2c893f6f3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ParamsEntity.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.oauth2.OAuth2Params; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = ModelConstants.OAUTH2_PARAMS_COLUMN_FAMILY_NAME) +@NoArgsConstructor +public class OAuth2ParamsEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_PARAMS_ENABLED_PROPERTY) + private Boolean enabled; + + @Column(name = ModelConstants.OAUTH2_PARAMS_TENANT_ID_PROPERTY) + private UUID tenantId; + + public OAuth2ParamsEntity(OAuth2Params oauth2Params) { + if (oauth2Params.getId() != null) { + this.setUuid(oauth2Params.getUuidId()); + } + this.setCreatedTime(oauth2Params.getCreatedTime()); + this.enabled = oauth2Params.isEnabled(); + if (oauth2Params.getTenantId() != null) { + this.tenantId = oauth2Params.getTenantId().getId(); + } + } + + @Override + public OAuth2Params toData() { + OAuth2Params oauth2Params = new OAuth2Params(); + oauth2Params.setId(new OAuth2ParamsId(id)); + oauth2Params.setCreatedTime(createdTime); + oauth2Params.setTenantId(new TenantId(tenantId)); + oauth2Params.setEnabled(enabled); + return oauth2Params; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java new file mode 100644 index 0000000000..9c6c260316 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java @@ -0,0 +1,212 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.id.OAuth2RegistrationId; +import org.thingsboard.server.common.data.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2BasicMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.TenantNameStrategyType; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.Arrays; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.OAUTH2_REGISTRATION_COLUMN_FAMILY_NAME) +public class OAuth2RegistrationEntity extends BaseSqlEntity { + + @Column(name = ModelConstants.OAUTH2_PARAMS_ID_PROPERTY) + private UUID oauth2ParamsId; + @Column(name = ModelConstants.OAUTH2_CLIENT_ID_PROPERTY) + private String clientId; + @Column(name = ModelConstants.OAUTH2_CLIENT_SECRET_PROPERTY) + private String clientSecret; + @Column(name = ModelConstants.OAUTH2_AUTHORIZATION_URI_PROPERTY) + private String authorizationUri; + @Column(name = ModelConstants.OAUTH2_TOKEN_URI_PROPERTY) + private String tokenUri; + @Column(name = ModelConstants.OAUTH2_SCOPE_PROPERTY) + private String scope; + @Column(name = ModelConstants.OAUTH2_USER_INFO_URI_PROPERTY) + private String userInfoUri; + @Column(name = ModelConstants.OAUTH2_USER_NAME_ATTRIBUTE_NAME_PROPERTY) + private String userNameAttributeName; + @Column(name = ModelConstants.OAUTH2_JWK_SET_URI_PROPERTY) + private String jwkSetUri; + @Column(name = ModelConstants.OAUTH2_CLIENT_AUTHENTICATION_METHOD_PROPERTY) + private String clientAuthenticationMethod; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY) + private String loginButtonLabel; + @Column(name = ModelConstants.OAUTH2_LOGIN_BUTTON_ICON_PROPERTY) + private String loginButtonIcon; + @Column(name = ModelConstants.OAUTH2_ALLOW_USER_CREATION_PROPERTY) + private Boolean allowUserCreation; + @Column(name = ModelConstants.OAUTH2_ACTIVATE_USER_PROPERTY) + private Boolean activateUser; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_MAPPER_TYPE_PROPERTY) + private MapperType type; + @Column(name = ModelConstants.OAUTH2_EMAIL_ATTRIBUTE_KEY_PROPERTY) + private String emailAttributeKey; + @Column(name = ModelConstants.OAUTH2_FIRST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String firstNameAttributeKey; + @Column(name = ModelConstants.OAUTH2_LAST_NAME_ATTRIBUTE_KEY_PROPERTY) + private String lastNameAttributeKey; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_STRATEGY_PROPERTY) + private TenantNameStrategyType tenantNameStrategy; + @Column(name = ModelConstants.OAUTH2_TENANT_NAME_PATTERN_PROPERTY) + private String tenantNamePattern; + @Column(name = ModelConstants.OAUTH2_CUSTOMER_NAME_PATTERN_PROPERTY) + private String customerNamePattern; + @Column(name = ModelConstants.OAUTH2_DEFAULT_DASHBOARD_NAME_PROPERTY) + private String defaultDashboardName; + @Column(name = ModelConstants.OAUTH2_ALWAYS_FULL_SCREEN_PROPERTY) + private Boolean alwaysFullScreen; + @Column(name = ModelConstants.OAUTH2_MAPPER_URL_PROPERTY) + private String url; + @Column(name = ModelConstants.OAUTH2_MAPPER_USERNAME_PROPERTY) + private String username; + @Column(name = ModelConstants.OAUTH2_MAPPER_PASSWORD_PROPERTY) + private String password; + @Column(name = ModelConstants.OAUTH2_MAPPER_SEND_TOKEN_PROPERTY) + private Boolean sendToken; + + @Type(type = "json") + @Column(name = ModelConstants.OAUTH2_ADDITIONAL_INFO_PROPERTY) + private JsonNode additionalInfo; + + public OAuth2RegistrationEntity() { + super(); + } + + public OAuth2RegistrationEntity(OAuth2Registration registration) { + if (registration.getId() != null) { + this.setUuid(registration.getId().getId()); + } + this.setCreatedTime(registration.getCreatedTime()); + if (registration.getOauth2ParamsId() != null) { + this.oauth2ParamsId = registration.getOauth2ParamsId().getId(); + } + this.clientId = registration.getClientId(); + this.clientSecret = registration.getClientSecret(); + this.authorizationUri = registration.getAuthorizationUri(); + this.tokenUri = registration.getAccessTokenUri(); + this.scope = registration.getScope().stream().reduce((result, element) -> result + "," + element).orElse(""); + this.userInfoUri = registration.getUserInfoUri(); + this.userNameAttributeName = registration.getUserNameAttributeName(); + this.jwkSetUri = registration.getJwkSetUri(); + this.clientAuthenticationMethod = registration.getClientAuthenticationMethod(); + this.loginButtonLabel = registration.getLoginButtonLabel(); + this.loginButtonIcon = registration.getLoginButtonIcon(); + this.additionalInfo = registration.getAdditionalInfo(); + OAuth2MapperConfig mapperConfig = registration.getMapperConfig(); + if (mapperConfig != null) { + this.allowUserCreation = mapperConfig.isAllowUserCreation(); + this.activateUser = mapperConfig.isActivateUser(); + this.type = mapperConfig.getType(); + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig != null) { + this.emailAttributeKey = basicConfig.getEmailAttributeKey(); + this.firstNameAttributeKey = basicConfig.getFirstNameAttributeKey(); + this.lastNameAttributeKey = basicConfig.getLastNameAttributeKey(); + this.tenantNameStrategy = basicConfig.getTenantNameStrategy(); + this.tenantNamePattern = basicConfig.getTenantNamePattern(); + this.customerNamePattern = basicConfig.getCustomerNamePattern(); + this.defaultDashboardName = basicConfig.getDefaultDashboardName(); + this.alwaysFullScreen = basicConfig.isAlwaysFullScreen(); + } + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig != null) { + this.url = customConfig.getUrl(); + this.username = customConfig.getUsername(); + this.password = customConfig.getPassword(); + this.sendToken = customConfig.isSendToken(); + } + } + } + + @Override + public OAuth2Registration toData() { + OAuth2Registration registration = new OAuth2Registration(); + registration.setId(new OAuth2RegistrationId(id)); + registration.setCreatedTime(createdTime); + registration.setOauth2ParamsId(new OAuth2ParamsId(oauth2ParamsId)); + registration.setAdditionalInfo(additionalInfo); + registration.setMapperConfig( + OAuth2MapperConfig.builder() + .allowUserCreation(allowUserCreation) + .activateUser(activateUser) + .type(type) + .basic( + (type == MapperType.BASIC || type == MapperType.GITHUB) ? + OAuth2BasicMapperConfig.builder() + .emailAttributeKey(emailAttributeKey) + .firstNameAttributeKey(firstNameAttributeKey) + .lastNameAttributeKey(lastNameAttributeKey) + .tenantNameStrategy(tenantNameStrategy) + .tenantNamePattern(tenantNamePattern) + .customerNamePattern(customerNamePattern) + .defaultDashboardName(defaultDashboardName) + .alwaysFullScreen(alwaysFullScreen) + .build() + : null + ) + .custom( + type == MapperType.CUSTOM ? + OAuth2CustomMapperConfig.builder() + .url(url) + .username(username) + .password(password) + .sendToken(sendToken) + .build() + : null + ) + .build() + ); + registration.setClientId(clientId); + registration.setClientSecret(clientSecret); + registration.setAuthorizationUri(authorizationUri); + registration.setAccessTokenUri(tokenUri); + registration.setScope(Arrays.asList(scope.split(","))); + registration.setUserInfoUri(userInfoUri); + registration.setUserNameAttributeName(userNameAttributeName); + registration.setJwkSetUri(jwkSetUri); + registration.setClientAuthenticationMethod(clientAuthenticationMethod); + registration.setLoginButtonLabel(loginButtonLabel); + registration.setLoginButtonIcon(loginButtonIcon); + return registration; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java similarity index 60% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java index e16d0417e4..a5291e8a79 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java @@ -20,11 +20,11 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -40,75 +40,79 @@ import javax.persistence.Table; import java.nio.ByteBuffer; import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ALGORITHM_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_SIZE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DEVICE_PROFILE_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_CHECKSUM_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_CONTENT_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DATA_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DATA_SIZE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @Data @EqualsAndHashCode(callSuper = true) @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) -@Table(name = FIRMWARE_TABLE_NAME) -public class FirmwareEntity extends BaseSqlEntity implements SearchTextEntity { +@Table(name = OTA_PACKAGE_TABLE_NAME) +public class OtaPackageEntity extends BaseSqlEntity implements SearchTextEntity { - @Column(name = FIRMWARE_TENANT_ID_COLUMN) + @Column(name = OTA_PACKAGE_TENANT_ID_COLUMN) private UUID tenantId; - @Column(name = FIRMWARE_DEVICE_PROFILE_ID_COLUMN) + @Column(name = OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN) private UUID deviceProfileId; @Enumerated(EnumType.STRING) - @Column(name = FIRMWARE_TYPE_COLUMN) - private FirmwareType type; + @Column(name = OTA_PACKAGE_TYPE_COLUMN) + private OtaPackageType type; - @Column(name = FIRMWARE_TITLE_COLUMN) + @Column(name = OTA_PACKAGE_TILE_COLUMN) private String title; - @Column(name = FIRMWARE_VERSION_COLUMN) + @Column(name = OTA_PACKAGE_VERSION_COLUMN) private String version; - @Column(name = FIRMWARE_FILE_NAME_COLUMN) + @Column(name = OTA_PACKAGE_URL_COLUMN) + private String url; + + @Column(name = OTA_PACKAGE_FILE_NAME_COLUMN) private String fileName; - @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) + @Column(name = OTA_PACKAGE_CONTENT_TYPE_COLUMN) private String contentType; @Enumerated(EnumType.STRING) - @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) + @Column(name = OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN) private ChecksumAlgorithm checksumAlgorithm; - @Column(name = FIRMWARE_CHECKSUM_COLUMN) + @Column(name = OTA_PACKAGE_CHECKSUM_COLUMN) private String checksum; @Lob - @Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY") + @Column(name = OTA_PACKAGE_DATA_COLUMN, columnDefinition = "BINARY") private byte[] data; - @Column(name = FIRMWARE_DATA_SIZE_COLUMN) + @Column(name = OTA_PACKAGE_DATA_SIZE_COLUMN) private Long dataSize; @Type(type = "json") - @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN) + @Column(name = ModelConstants.OTA_PACKAGE_ADDITIONAL_INFO_COLUMN) private JsonNode additionalInfo; @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; - public FirmwareEntity() { + public OtaPackageEntity() { super(); } - public FirmwareEntity(Firmware firmware) { + public OtaPackageEntity(OtaPackage firmware) { this.createdTime = firmware.getCreatedTime(); this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); @@ -118,6 +122,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex this.type = firmware.getType(); this.title = firmware.getTitle(); this.version = firmware.getVersion(); + this.url = firmware.getUrl(); this.fileName = firmware.getFileName(); this.contentType = firmware.getContentType(); this.checksumAlgorithm = firmware.getChecksumAlgorithm(); @@ -138,8 +143,8 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex } @Override - public Firmware toData() { - Firmware firmware = new Firmware(new FirmwareId(id)); + public OtaPackage toData() { + OtaPackage firmware = new OtaPackage(new OtaPackageId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); if (deviceProfileId != null) { @@ -148,6 +153,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); + firmware.setUrl(url); firmware.setFileName(fileName); firmware.setContentType(contentType); firmware.setChecksumAlgorithm(checksumAlgorithm); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java similarity index 60% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java index 93a6e83f25..db16251f71 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java @@ -21,11 +21,12 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.FirmwareInfo; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -40,60 +41,64 @@ import javax.persistence.Table; import javax.persistence.Transient; import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ALGORITHM_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_SIZE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DEVICE_PROFILE_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_CHECKSUM_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_CONTENT_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DATA_SIZE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_FILE_NAME_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @Data @EqualsAndHashCode(callSuper = true) @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) -@Table(name = FIRMWARE_TABLE_NAME) -public class FirmwareInfoEntity extends BaseSqlEntity implements SearchTextEntity { +@Table(name = OTA_PACKAGE_TABLE_NAME) +public class OtaPackageInfoEntity extends BaseSqlEntity implements SearchTextEntity { - @Column(name = FIRMWARE_TENANT_ID_COLUMN) + @Column(name = OTA_PACKAGE_TENANT_ID_COLUMN) private UUID tenantId; - @Column(name = FIRMWARE_DEVICE_PROFILE_ID_COLUMN) + @Column(name = OTA_PACKAGE_DEVICE_PROFILE_ID_COLUMN) private UUID deviceProfileId; @Enumerated(EnumType.STRING) - @Column(name = FIRMWARE_TYPE_COLUMN) - private FirmwareType type; + @Column(name = OTA_PACKAGE_TYPE_COLUMN) + private OtaPackageType type; - @Column(name = FIRMWARE_TITLE_COLUMN) + @Column(name = OTA_PACKAGE_TILE_COLUMN) private String title; - @Column(name = FIRMWARE_VERSION_COLUMN) + @Column(name = OTA_PACKAGE_VERSION_COLUMN) private String version; - @Column(name = FIRMWARE_FILE_NAME_COLUMN) + @Column(name = OTA_PACKAGE_URL_COLUMN) + private String url; + + @Column(name = OTA_PACKAGE_FILE_NAME_COLUMN) private String fileName; - @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) + @Column(name = OTA_PACKAGE_CONTENT_TYPE_COLUMN) private String contentType; @Enumerated(EnumType.STRING) - @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) + @Column(name = OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN) private ChecksumAlgorithm checksumAlgorithm; - @Column(name = FIRMWARE_CHECKSUM_COLUMN) + @Column(name = OTA_PACKAGE_CHECKSUM_COLUMN) private String checksum; - @Column(name = FIRMWARE_DATA_SIZE_COLUMN) + @Column(name = OTA_PACKAGE_DATA_SIZE_COLUMN) private Long dataSize; @Type(type = "json") - @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN) + @Column(name = ModelConstants.OTA_PACKAGE_ADDITIONAL_INFO_COLUMN) private JsonNode additionalInfo; @Column(name = SEARCH_TEXT_PROPERTY) @@ -102,11 +107,11 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Transient private boolean hasData; - public FirmwareInfoEntity() { + public OtaPackageInfoEntity() { super(); } - public FirmwareInfoEntity(FirmwareInfo firmware) { + public OtaPackageInfoEntity(OtaPackageInfo firmware) { this.createdTime = firmware.getCreatedTime(); this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); @@ -116,6 +121,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S } this.title = firmware.getTitle(); this.version = firmware.getVersion(); + this.url = firmware.getUrl(); this.fileName = firmware.getFileName(); this.contentType = firmware.getContentType(); this.checksumAlgorithm = firmware.getChecksumAlgorithm(); @@ -124,9 +130,9 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.additionalInfo = firmware.getAdditionalInfo(); } - public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, FirmwareType type, String title, String version, - String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize, - Object additionalInfo, boolean hasData) { + public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version, + String url, String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize, + Object additionalInfo, boolean hasData) { this.id = id; this.createdTime = createdTime; this.tenantId = tenantId; @@ -134,6 +140,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.type = type; this.title = title; this.version = version; + this.url = url; this.fileName = fileName; this.contentType = contentType; this.checksumAlgorithm = checksumAlgorithm; @@ -154,8 +161,8 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S } @Override - public FirmwareInfo toData() { - FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id)); + public OtaPackageInfo toData() { + OtaPackageInfo firmware = new OtaPackageInfo(new OtaPackageId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); if (deviceProfileId != null) { @@ -164,6 +171,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); + firmware.setUrl(url); firmware.setFileName(fileName); firmware.setContentType(contentType); firmware.setChecksumAlgorithm(checksumAlgorithm); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/AbstractOAuth2ClientRegistrationInfoEntity.java similarity index 97% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/AbstractOAuth2ClientRegistrationInfoEntity.java index 4799087457..616f128920 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractOAuth2ClientRegistrationInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/AbstractOAuth2ClientRegistrationInfoEntity.java @@ -13,22 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sql; +package org.thingsboard.server.dao.model.sql.deprecated; import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId; import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.sql.deprecated.OAuth2ClientRegistrationInfoEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.*; import java.util.Arrays; +@Deprecated @Data @EqualsAndHashCode(callSuper = true) @TypeDef(name = "json", typeClass = JsonStringType.class) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/ExtendedOAuth2ClientRegistrationInfoEntity.java similarity index 91% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/ExtendedOAuth2ClientRegistrationInfoEntity.java index 129bcf730b..beb525e2e0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ExtendedOAuth2ClientRegistrationInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/ExtendedOAuth2ClientRegistrationInfoEntity.java @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sql; +package org.thingsboard.server.dao.model.sql.deprecated; import lombok.Data; import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.ExtendedOAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.SchemeType; +@Deprecated @Data @EqualsAndHashCode(callSuper = true) public class ExtendedOAuth2ClientRegistrationInfoEntity extends AbstractOAuth2ClientRegistrationInfoEntity { diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/OAuth2ClientRegistrationEntity.java similarity index 89% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/OAuth2ClientRegistrationEntity.java index 505f2facf1..9de0b9daea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/OAuth2ClientRegistrationEntity.java @@ -13,22 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sql; +package org.thingsboard.server.dao.model.sql.deprecated; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationId; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationId; +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId; import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistration; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.*; -import java.util.Arrays; import java.util.UUID; +@Deprecated @Data @EqualsAndHashCode(callSuper = true) @Entity diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/OAuth2ClientRegistrationInfoEntity.java similarity index 91% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/OAuth2ClientRegistrationInfoEntity.java index 1a379baf23..fbb9ec3fca 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2ClientRegistrationInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/deprecated/OAuth2ClientRegistrationInfoEntity.java @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.model.sql; +package org.thingsboard.server.dao.model.sql.deprecated; import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Entity; import javax.persistence.Table; +@Deprecated @Data @EqualsAndHashCode(callSuper = true) @Entity diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java index 7361dc21d2..feba5c0158 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java @@ -21,7 +21,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.ClientAuthenticationMethod; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import java.util.UUID; @@ -34,25 +34,25 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep @Override public ClientRegistration findByRegistrationId(String registrationId) { - OAuth2ClientRegistrationInfo oAuth2ClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(UUID.fromString(registrationId)); - return oAuth2ClientRegistrationInfo == null ? - null : toSpringClientRegistration(oAuth2ClientRegistrationInfo); + OAuth2Registration registration = oAuth2Service.findRegistration(UUID.fromString(registrationId)); + return registration == null ? + null : toSpringClientRegistration(registration); } - private ClientRegistration toSpringClientRegistration(OAuth2ClientRegistrationInfo localClientRegistration){ - String registrationId = localClientRegistration.getUuidId().toString(); + private ClientRegistration toSpringClientRegistration(OAuth2Registration registration){ + String registrationId = registration.getUuidId().toString(); return ClientRegistration.withRegistrationId(registrationId) - .clientName(localClientRegistration.getName()) - .clientId(localClientRegistration.getClientId()) - .authorizationUri(localClientRegistration.getAuthorizationUri()) - .clientSecret(localClientRegistration.getClientSecret()) - .tokenUri(localClientRegistration.getAccessTokenUri()) - .scope(localClientRegistration.getScope()) + .clientName(registration.getName()) + .clientId(registration.getClientId()) + .authorizationUri(registration.getAuthorizationUri()) + .clientSecret(registration.getClientSecret()) + .tokenUri(registration.getAccessTokenUri()) + .scope(registration.getScope()) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) - .userInfoUri(localClientRegistration.getUserInfoUri()) - .userNameAttributeName(localClientRegistration.getUserNameAttributeName()) - .jwkSetUri(localClientRegistration.getJwkSetUri()) - .clientAuthenticationMethod(new ClientAuthenticationMethod(localClientRegistration.getClientAuthenticationMethod())) + .userInfoUri(registration.getUserInfoUri()) + .userNameAttributeName(registration.getUserNameAttributeName()) + .jwkSetUri(registration.getJwkSetUri()) + .clientAuthenticationMethod(new ClientAuthenticationMethod(registration.getClientAuthenticationMethod())) .redirectUri(defaultRedirectUriTemplate) .build(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java new file mode 100644 index 0000000000..78c237d943 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2DomainDao.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.OAuth2Domain; +import org.thingsboard.server.dao.Dao; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2DomainDao extends Dao { + + List findByOAuth2ParamsId(UUID oauth2ParamsId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java new file mode 100644 index 0000000000..3bccb61b0b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2MobileDao.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; +import org.thingsboard.server.dao.Dao; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2MobileDao extends Dao { + + List findByOAuth2ParamsId(UUID oauth2ParamsId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ParamsDao.java similarity index 77% rename from dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java rename to dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ParamsDao.java index 0cacb47ea0..5821431444 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ParamsDao.java @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.firmware; +package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.oauth2.OAuth2Params; import org.thingsboard.server.dao.Dao; -public interface FirmwareDao extends Dao { - +public interface OAuth2ParamsDao extends Dao { + void deleteAll(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java new file mode 100644 index 0000000000..ad12b12e48 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2RegistrationDao.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.oauth2; + +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.Dao; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2RegistrationDao extends Dao { + + List findEnabledByDomainSchemesDomainNameAndPkgName(List domainSchemes, String domainName, String pkgName); + + List findByOAuth2ParamsId(UUID oauth2ParamsId); + + String findCallbackUrlScheme(UUID id, String pkgName); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index acde25f8ed..8d06adee0b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -19,11 +19,21 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.common.data.oauth2.deprecated.ClientRegistrationDto; +import org.thingsboard.server.common.data.oauth2.deprecated.DomainInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsDomainParams; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; 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.oauth2.deprecated.OAuth2ClientRegistrationDao; +import org.thingsboard.server.dao.oauth2.deprecated.OAuth2ClientRegistrationInfoDao; import javax.transaction.Transactional; import java.util.*; @@ -45,9 +55,17 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se private OAuth2ClientRegistrationInfoDao clientRegistrationInfoDao; @Autowired private OAuth2ClientRegistrationDao clientRegistrationDao; + @Autowired + private OAuth2ParamsDao oauth2ParamsDao; + @Autowired + private OAuth2RegistrationDao oauth2RegistrationDao; + @Autowired + private OAuth2DomainDao oauth2DomainDao; + @Autowired + private OAuth2MobileDao oauth2MobileDao; @Override - public List getOAuth2Clients(String domainSchemeStr, String domainName) { + public List getOAuth2Clients(String domainSchemeStr, String domainName, String pkgName) { log.trace("Executing getOAuth2Clients [{}://{}]", domainSchemeStr, domainName); if (domainSchemeStr == null) { throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); @@ -59,12 +77,12 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se throw new IncorrectParameterException(INCORRECT_DOMAIN_SCHEME); } validateString(domainName, INCORRECT_DOMAIN_NAME + domainName); - return clientRegistrationInfoDao.findByDomainSchemesAndDomainName(Arrays.asList(domainScheme, SchemeType.MIXED), domainName).stream() - .filter(OAuth2ClientRegistrationInfo::isEnabled) + return oauth2RegistrationDao.findEnabledByDomainSchemesDomainNameAndPkgName(Arrays.asList(domainScheme, SchemeType.MIXED), domainName, pkgName).stream() .map(OAuth2Utils::toClientInfo) .collect(Collectors.toList()); } + @Deprecated @Override @Transactional public void saveOAuth2Params(OAuth2ClientsParams oauth2Params) { @@ -85,6 +103,33 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se }); } + @Override + @Transactional + public void saveOAuth2Info(OAuth2Info oauth2Info) { + log.trace("Executing saveOAuth2Info [{}]", oauth2Info); + oauth2InfoValidator.accept(oauth2Info); + oauth2ParamsDao.deleteAll(); + oauth2Info.getOauth2ParamsInfos().forEach(oauth2ParamsInfo -> { + OAuth2Params oauth2Params = OAuth2Utils.infoToOAuth2Params(oauth2Info); + OAuth2Params savedOauth2Params = oauth2ParamsDao.save(TenantId.SYS_TENANT_ID, oauth2Params); + oauth2ParamsInfo.getClientRegistrations().forEach(registrationInfo -> { + OAuth2Registration registration = OAuth2Utils.toOAuth2Registration(savedOauth2Params.getId(), registrationInfo); + oauth2RegistrationDao.save(TenantId.SYS_TENANT_ID, registration); + }); + oauth2ParamsInfo.getDomainInfos().forEach(domainInfo -> { + OAuth2Domain domain = OAuth2Utils.toOAuth2Domain(savedOauth2Params.getId(), domainInfo); + oauth2DomainDao.save(TenantId.SYS_TENANT_ID, domain); + }); + if (oauth2ParamsInfo.getMobileInfos() != null) { + oauth2ParamsInfo.getMobileInfos().forEach(mobileInfo -> { + OAuth2Mobile mobile = OAuth2Utils.toOAuth2Mobile(savedOauth2Params.getId(), mobileInfo); + oauth2MobileDao.save(TenantId.SYS_TENANT_ID, mobile); + }); + } + }); + } + + @Deprecated @Override public OAuth2ClientsParams findOAuth2Params() { log.trace("Executing findOAuth2Params"); @@ -93,16 +138,42 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } @Override - public OAuth2ClientRegistrationInfo findClientRegistrationInfo(UUID id) { - log.trace("Executing findClientRegistrationInfo [{}]", id); + public OAuth2Info findOAuth2Info() { + log.trace("Executing findOAuth2Info"); + OAuth2Info oauth2Info = new OAuth2Info(); + List oauth2ParamsList = oauth2ParamsDao.find(TenantId.SYS_TENANT_ID); + oauth2Info.setEnabled(oauth2ParamsList.stream().anyMatch(param -> param.isEnabled())); + List oauth2ParamsInfos = new ArrayList<>(); + oauth2Info.setOauth2ParamsInfos(oauth2ParamsInfos); + oauth2ParamsList.stream().sorted(Comparator.comparing(BaseData::getUuidId)).forEach(oauth2Params -> { + List registrations = oauth2RegistrationDao.findByOAuth2ParamsId(oauth2Params.getId().getId()); + List domains = oauth2DomainDao.findByOAuth2ParamsId(oauth2Params.getId().getId()); + List mobiles = oauth2MobileDao.findByOAuth2ParamsId(oauth2Params.getId().getId()); + oauth2ParamsInfos.add(OAuth2Utils.toOAuth2ParamsInfo(registrations, domains, mobiles)); + }); + return oauth2Info; + } + + @Override + public OAuth2Registration findRegistration(UUID id) { + log.trace("Executing findRegistration [{}]", id); + validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); + return oauth2RegistrationDao.findById(null, id); + } + + @Override + public String findCallbackUrlScheme(UUID id, String pkgName) { + log.trace("Executing findCallbackUrlScheme [{}][{}]", id, pkgName); validateId(id, INCORRECT_CLIENT_REGISTRATION_ID + id); - return clientRegistrationInfoDao.findById(null, id); + validateString(pkgName, "Incorrect package name"); + return oauth2RegistrationDao.findCallbackUrlScheme(id, pkgName); } + @Override - public List findAllClientRegistrationInfos() { - log.trace("Executing findAllClientRegistrationInfos"); - return clientRegistrationInfoDao.findAll(); + public List findAllRegistrations() { + log.trace("Executing findAllRegistrations"); + return oauth2RegistrationDao.find(TenantId.SYS_TENANT_ID); } private final Consumer clientParamsValidator = oauth2Params -> { @@ -212,4 +283,136 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se } } }; + + private final Consumer oauth2InfoValidator = oauth2Info -> { + if (oauth2Info == null + || oauth2Info.getOauth2ParamsInfos() == null) { + throw new DataValidationException("OAuth2 param infos should be specified!"); + } + for (OAuth2ParamsInfo oauth2Params : oauth2Info.getOauth2ParamsInfos()) { + if (oauth2Params.getDomainInfos() == null + || oauth2Params.getDomainInfos().isEmpty()) { + throw new DataValidationException("List of domain configuration should be specified!"); + } + for (OAuth2DomainInfo domainInfo : oauth2Params.getDomainInfos()) { + if (StringUtils.isEmpty(domainInfo.getName())) { + throw new DataValidationException("Domain name should be specified!"); + } + if (domainInfo.getScheme() == null) { + throw new DataValidationException("Domain scheme should be specified!"); + } + } + oauth2Params.getDomainInfos().stream() + .collect(Collectors.groupingBy(OAuth2DomainInfo::getName)) + .forEach((domainName, domainInfos) -> { + if (domainInfos.size() > 1 && domainInfos.stream().anyMatch(domainInfo -> domainInfo.getScheme() == SchemeType.MIXED)) { + throw new DataValidationException("MIXED scheme type shouldn't be combined with another scheme type!"); + } + domainInfos.stream() + .collect(Collectors.groupingBy(OAuth2DomainInfo::getScheme)) + .forEach((schemeType, domainInfosBySchemeType) -> { + if (domainInfosBySchemeType.size() > 1) { + throw new DataValidationException("Domain name and protocol must be unique within OAuth2 parameters!"); + } + }); + }); + if (oauth2Params.getMobileInfos() != null) { + for (OAuth2MobileInfo mobileInfo : oauth2Params.getMobileInfos()) { + if (StringUtils.isEmpty(mobileInfo.getPkgName())) { + throw new DataValidationException("Package should be specified!"); + } + if (StringUtils.isEmpty(mobileInfo.getCallbackUrlScheme())) { + throw new DataValidationException("Callback URL scheme should be specified!"); + } + } + oauth2Params.getMobileInfos().stream() + .collect(Collectors.groupingBy(OAuth2MobileInfo::getPkgName)) + .forEach((pkgName, mobileInfos) -> { + if (mobileInfos.size() > 1) { + throw new DataValidationException("Mobile app package name must be unique within OAuth2 parameters!"); + } + }); + } + if (oauth2Params.getClientRegistrations() == null || oauth2Params.getClientRegistrations().isEmpty()) { + throw new DataValidationException("Client registrations should be specified!"); + } + for (OAuth2RegistrationInfo clientRegistration : oauth2Params.getClientRegistrations()) { + if (StringUtils.isEmpty(clientRegistration.getClientId())) { + throw new DataValidationException("Client ID should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientSecret())) { + throw new DataValidationException("Client secret should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAuthorizationUri())) { + throw new DataValidationException("Authorization uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getAccessTokenUri())) { + throw new DataValidationException("Token uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getScope())) { + throw new DataValidationException("Scope should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { + throw new DataValidationException("User info uri should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { + throw new DataValidationException("User name attribute name should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getClientAuthenticationMethod())) { + throw new DataValidationException("Client authentication method should be specified!"); + } + if (StringUtils.isEmpty(clientRegistration.getLoginButtonLabel())) { + throw new DataValidationException("Login button label should be specified!"); + } + OAuth2MapperConfig mapperConfig = clientRegistration.getMapperConfig(); + if (mapperConfig == null) { + throw new DataValidationException("Mapper config should be specified!"); + } + if (mapperConfig.getType() == null) { + throw new DataValidationException("Mapper config type should be specified!"); + } + if (mapperConfig.getType() == MapperType.BASIC) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } + if (mapperConfig.getType() == MapperType.GITHUB) { + OAuth2BasicMapperConfig basicConfig = mapperConfig.getBasic(); + if (basicConfig == null) { + throw new DataValidationException("Basic config should be specified!"); + } + if (!StringUtils.isEmpty(basicConfig.getEmailAttributeKey())) { + throw new DataValidationException("Email attribute key cannot be configured for GITHUB mapper type!"); + } + if (basicConfig.getTenantNameStrategy() == null) { + throw new DataValidationException("Tenant name strategy should be specified!"); + } + if (basicConfig.getTenantNameStrategy() == TenantNameStrategyType.CUSTOM + && StringUtils.isEmpty(basicConfig.getTenantNamePattern())) { + throw new DataValidationException("Tenant name pattern should be specified!"); + } + } + if (mapperConfig.getType() == MapperType.CUSTOM) { + OAuth2CustomMapperConfig customConfig = mapperConfig.getCustom(); + if (customConfig == null) { + throw new DataValidationException("Custom config should be specified!"); + } + if (StringUtils.isEmpty(customConfig.getUrl())) { + throw new DataValidationException("Custom mapper URL should be specified!"); + } + } + } + } + }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java index 2a59ccb829..dd128b435b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2Utils.java @@ -15,19 +15,30 @@ */ package org.thingsboard.server.dao.oauth2; -import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationInfoId; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.id.OAuth2ParamsId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.deprecated.OAuth2ClientRegistrationInfoId; import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.common.data.oauth2.deprecated.ClientRegistrationDto; +import org.thingsboard.server.common.data.oauth2.deprecated.DomainInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsDomainParams; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; import java.util.*; +import java.util.stream.Collectors; public class OAuth2Utils { public static final String OAUTH2_AUTHORIZATION_PATH_TEMPLATE = "/oauth2/authorization/%s"; - public static OAuth2ClientInfo toClientInfo(OAuth2ClientRegistrationInfo clientRegistrationInfo) { + public static OAuth2ClientInfo toClientInfo(OAuth2Registration registration) { OAuth2ClientInfo client = new OAuth2ClientInfo(); - client.setName(clientRegistrationInfo.getLoginButtonLabel()); - client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, clientRegistrationInfo.getUuidId().toString())); - client.setIcon(clientRegistrationInfo.getLoginButtonIcon()); + client.setName(registration.getLoginButtonLabel()); + client.setUrl(String.format(OAUTH2_AUTHORIZATION_PATH_TEMPLATE, registration.getUuidId().toString())); + client.setIcon(registration.getLoginButtonIcon()); return client; } @@ -99,4 +110,127 @@ public class OAuth2Utils { clientRegistration.setDomainScheme(domainScheme); return clientRegistration; } + + public static OAuth2ParamsInfo toOAuth2ParamsInfo(List registrations, List domains, List mobiles) { + OAuth2ParamsInfo oauth2ParamsInfo = new OAuth2ParamsInfo(); + oauth2ParamsInfo.setClientRegistrations(registrations.stream().sorted(Comparator.comparing(BaseData::getUuidId)).map(OAuth2Utils::toOAuth2RegistrationInfo).collect(Collectors.toList())); + oauth2ParamsInfo.setDomainInfos(domains.stream().sorted(Comparator.comparing(BaseData::getUuidId)).map(OAuth2Utils::toOAuth2DomainInfo).collect(Collectors.toList())); + oauth2ParamsInfo.setMobileInfos(mobiles.stream().sorted(Comparator.comparing(BaseData::getUuidId)).map(OAuth2Utils::toOAuth2MobileInfo).collect(Collectors.toList())); + return oauth2ParamsInfo; + } + + public static OAuth2RegistrationInfo toOAuth2RegistrationInfo(OAuth2Registration registration) { + return OAuth2RegistrationInfo.builder() + .mapperConfig(registration.getMapperConfig()) + .clientId(registration.getClientId()) + .clientSecret(registration.getClientSecret()) + .authorizationUri(registration.getAuthorizationUri()) + .accessTokenUri(registration.getAccessTokenUri()) + .scope(registration.getScope()) + .userInfoUri(registration.getUserInfoUri()) + .userNameAttributeName(registration.getUserNameAttributeName()) + .jwkSetUri(registration.getJwkSetUri()) + .clientAuthenticationMethod(registration.getClientAuthenticationMethod()) + .loginButtonLabel(registration.getLoginButtonLabel()) + .loginButtonIcon(registration.getLoginButtonIcon()) + .additionalInfo(registration.getAdditionalInfo()) + .build(); + } + + public static OAuth2DomainInfo toOAuth2DomainInfo(OAuth2Domain domain) { + return OAuth2DomainInfo.builder() + .name(domain.getDomainName()) + .scheme(domain.getDomainScheme()) + .build(); + } + + public static OAuth2MobileInfo toOAuth2MobileInfo(OAuth2Mobile mobile) { + return OAuth2MobileInfo.builder() + .pkgName(mobile.getPkgName()) + .callbackUrlScheme(mobile.getCallbackUrlScheme()) + .build(); + } + + public static OAuth2Params infoToOAuth2Params(OAuth2Info oauth2Info) { + OAuth2Params oauth2Params = new OAuth2Params(); + oauth2Params.setEnabled(oauth2Info.isEnabled()); + oauth2Params.setTenantId(TenantId.SYS_TENANT_ID); + return oauth2Params; + } + + public static OAuth2Registration toOAuth2Registration(OAuth2ParamsId oauth2ParamsId, OAuth2RegistrationInfo registrationInfo) { + OAuth2Registration registration = new OAuth2Registration(); + registration.setOauth2ParamsId(oauth2ParamsId); + registration.setMapperConfig(registrationInfo.getMapperConfig()); + registration.setClientId(registrationInfo.getClientId()); + registration.setClientSecret(registrationInfo.getClientSecret()); + registration.setAuthorizationUri(registrationInfo.getAuthorizationUri()); + registration.setAccessTokenUri(registrationInfo.getAccessTokenUri()); + registration.setScope(registrationInfo.getScope()); + registration.setUserInfoUri(registrationInfo.getUserInfoUri()); + registration.setUserNameAttributeName(registrationInfo.getUserNameAttributeName()); + registration.setJwkSetUri(registrationInfo.getJwkSetUri()); + registration.setClientAuthenticationMethod(registrationInfo.getClientAuthenticationMethod()); + registration.setLoginButtonLabel(registrationInfo.getLoginButtonLabel()); + registration.setLoginButtonIcon(registrationInfo.getLoginButtonIcon()); + registration.setAdditionalInfo(registrationInfo.getAdditionalInfo()); + return registration; + } + + public static OAuth2Domain toOAuth2Domain(OAuth2ParamsId oauth2ParamsId, OAuth2DomainInfo domainInfo) { + OAuth2Domain domain = new OAuth2Domain(); + domain.setOauth2ParamsId(oauth2ParamsId); + domain.setDomainName(domainInfo.getName()); + domain.setDomainScheme(domainInfo.getScheme()); + return domain; + } + + public static OAuth2Mobile toOAuth2Mobile(OAuth2ParamsId oauth2ParamsId, OAuth2MobileInfo mobileInfo) { + OAuth2Mobile mobile = new OAuth2Mobile(); + mobile.setOauth2ParamsId(oauth2ParamsId); + mobile.setPkgName(mobileInfo.getPkgName()); + mobile.setCallbackUrlScheme(mobileInfo.getCallbackUrlScheme()); + return mobile; + } + + @Deprecated + public static OAuth2Info clientParamsToOAuth2Info(OAuth2ClientsParams clientsParams) { + OAuth2Info oauth2Info = new OAuth2Info(); + oauth2Info.setEnabled(clientsParams.isEnabled()); + oauth2Info.setOauth2ParamsInfos(clientsParams.getDomainsParams().stream().map(OAuth2Utils::clientsDomainParamsToOAuth2ParamsInfo).collect(Collectors.toList())); + return oauth2Info; + } + + private static OAuth2ParamsInfo clientsDomainParamsToOAuth2ParamsInfo(OAuth2ClientsDomainParams clientsDomainParams) { + OAuth2ParamsInfo oauth2ParamsInfo = new OAuth2ParamsInfo(); + oauth2ParamsInfo.setMobileInfos(Collections.emptyList()); + oauth2ParamsInfo.setClientRegistrations(clientsDomainParams.getClientRegistrations().stream().map(OAuth2Utils::clientRegistrationDtoToOAuth2RegistrationInfo).collect(Collectors.toList())); + oauth2ParamsInfo.setDomainInfos(clientsDomainParams.getDomainInfos().stream().map(OAuth2Utils::domainInfoToOAuth2DomainInfo).collect(Collectors.toList())); + return oauth2ParamsInfo; + } + + private static OAuth2RegistrationInfo clientRegistrationDtoToOAuth2RegistrationInfo(ClientRegistrationDto clientRegistrationDto) { + return OAuth2RegistrationInfo.builder() + .mapperConfig(clientRegistrationDto.getMapperConfig()) + .clientId(clientRegistrationDto.getClientId()) + .clientSecret(clientRegistrationDto.getClientSecret()) + .authorizationUri(clientRegistrationDto.getAuthorizationUri()) + .accessTokenUri(clientRegistrationDto.getAccessTokenUri()) + .scope(clientRegistrationDto.getScope()) + .userInfoUri(clientRegistrationDto.getUserInfoUri()) + .userNameAttributeName(clientRegistrationDto.getUserNameAttributeName()) + .jwkSetUri(clientRegistrationDto.getJwkSetUri()) + .clientAuthenticationMethod(clientRegistrationDto.getClientAuthenticationMethod()) + .loginButtonLabel(clientRegistrationDto.getLoginButtonLabel()) + .loginButtonIcon(clientRegistrationDto.getLoginButtonIcon()) + .additionalInfo(clientRegistrationDto.getAdditionalInfo()) + .build(); + } + + private static OAuth2DomainInfo domainInfoToOAuth2DomainInfo(DomainInfo domainInfo) { + return OAuth2DomainInfo.builder() + .name(domainInfo.getName()) + .scheme(domainInfo.getScheme()) + .build(); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/deprecated/OAuth2ClientRegistrationDao.java similarity index 83% rename from dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java rename to dao/src/main/java/org/thingsboard/server/dao/oauth2/deprecated/OAuth2ClientRegistrationDao.java index 2f4f2c1be2..63b2c46034 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/deprecated/OAuth2ClientRegistrationDao.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.oauth2; +package org.thingsboard.server.dao.oauth2.deprecated; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistration; import org.thingsboard.server.dao.Dao; +@Deprecated public interface OAuth2ClientRegistrationDao extends Dao { void deleteAll(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/deprecated/OAuth2ClientRegistrationInfoDao.java similarity index 81% rename from dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java rename to dao/src/main/java/org/thingsboard/server/dao/oauth2/deprecated/OAuth2ClientRegistrationInfoDao.java index 6fe93bacf4..9f44335602 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientRegistrationInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/deprecated/OAuth2ClientRegistrationInfoDao.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.oauth2; +package org.thingsboard.server.dao.oauth2.deprecated; -import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.dao.Dao; import java.util.List; -import java.util.Set; +@Deprecated public interface OAuth2ClientRegistrationInfoDao extends Dao { List findAll(); 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 new file mode 100644 index 0000000000..881baea4f1 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java @@ -0,0 +1,399 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.ota; + +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; +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; +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.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; + +@Service +@Slf4j +@RequiredArgsConstructor +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; + + @Override + public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) { + log.trace("Executing saveOtaPackageInfo [{}]", otaPackageInfo); + otaPackageInfoValidator.validate(otaPackageInfo, OtaPackageInfo::getTenantId); + try { + OtaPackageId otaPackageId = otaPackageInfo.getId(); + if (otaPackageId != null) { + Cache cache = cacheManager.getCache(OTA_PACKAGE_CACHE); + cache.evict(toOtaPackageInfoKey(otaPackageId)); + otaPackageDataCache.evict(otaPackageId.toString()); + } + return otaPackageInfoDao.save(otaPackageInfo.getTenantId(), otaPackageInfo); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("ota_package_tenant_title_version_unq_key")) { + throw new DataValidationException("OtaPackage with such title and version already exists!"); + } else { + throw t; + } + } + } + + @Override + public OtaPackage saveOtaPackage(OtaPackage otaPackage) { + log.trace("Executing saveOtaPackage [{}]", otaPackage); + otaPackageValidator.validate(otaPackage, OtaPackageInfo::getTenantId); + try { + OtaPackageId otaPackageId = otaPackage.getId(); + if (otaPackageId != null) { + Cache cache = cacheManager.getCache(OTA_PACKAGE_CACHE); + cache.evict(toOtaPackageInfoKey(otaPackageId)); + otaPackageDataCache.evict(otaPackageId.toString()); + } + return otaPackageDao.save(otaPackage.getTenantId(), otaPackage); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("ota_package_tenant_title_version_unq_key")) { + throw new DataValidationException("OtaPackage with such title and version already exists!"); + } else { + throw t; + } + } + } + + @Override + public String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) { + if (data == null || !data.hasArray() || data.array().length == 0) { + throw new DataValidationException("OtaPackage data should be specified!"); + } + + return getHashFunction(checksumAlgorithm).hashBytes(data.array()).toString(); + } + + private HashFunction getHashFunction(ChecksumAlgorithm checksumAlgorithm) { + switch (checksumAlgorithm) { + case MD5: + return Hashing.md5(); + case SHA256: + return Hashing.sha256(); + case SHA384: + return Hashing.sha384(); + case SHA512: + return Hashing.sha512(); + case CRC32: + return Hashing.crc32(); + case MURMUR3_32: + return Hashing.murmur3_32(); + case MURMUR3_128: + return Hashing.murmur3_128(); + default: + throw new DataValidationException("Unknown checksum algorithm!"); + } + } + + @Override + public OtaPackage findOtaPackageById(TenantId tenantId, OtaPackageId otaPackageId) { + log.trace("Executing findOtaPackageById [{}]", otaPackageId); + validateId(otaPackageId, INCORRECT_OTA_PACKAGE_ID + otaPackageId); + return otaPackageDao.findById(tenantId, otaPackageId.getId()); + } + + @Override + @Cacheable(cacheNames = OTA_PACKAGE_CACHE, key = "{#otaPackageId}") + public OtaPackageInfo findOtaPackageInfoById(TenantId tenantId, OtaPackageId otaPackageId) { + log.trace("Executing findOtaPackageInfoById [{}]", otaPackageId); + validateId(otaPackageId, INCORRECT_OTA_PACKAGE_ID + otaPackageId); + return otaPackageInfoDao.findById(tenantId, otaPackageId.getId()); + } + + @Override + public ListenableFuture findOtaPackageInfoByIdAsync(TenantId tenantId, OtaPackageId otaPackageId) { + log.trace("Executing findOtaPackageInfoByIdAsync [{}]", otaPackageId); + validateId(otaPackageId, INCORRECT_OTA_PACKAGE_ID + otaPackageId); + return otaPackageInfoDao.findByIdAsync(tenantId, otaPackageId.getId()); + } + + @Override + public PageData findTenantOtaPackagesByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findTenantOtaPackagesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return otaPackageInfoDao.findOtaPackageInfoByTenantId(tenantId, pageLink); + } + + @Override + public PageData findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) { + log.trace("Executing findTenantOtaPackagesByTenantIdAndHasData, tenantId [{}], pageLink [{}]", tenantId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return otaPackageInfoDao.findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, otaPackageType, pageLink); + } + + @Override + public void deleteOtaPackage(TenantId tenantId, OtaPackageId otaPackageId) { + log.trace("Executing deleteOtaPackage [{}]", otaPackageId); + validateId(otaPackageId, INCORRECT_OTA_PACKAGE_ID + otaPackageId); + try { + Cache cache = cacheManager.getCache(OTA_PACKAGE_CACHE); + cache.evict(toOtaPackageInfoKey(otaPackageId)); + otaPackageDataCache.evict(otaPackageId.toString()); + otaPackageDao.removeById(tenantId, otaPackageId.getId()); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device")) { + throw new DataValidationException("The otaPackage referenced by the devices cannot be deleted!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device_profile")) { + throw new DataValidationException("The otaPackage referenced by the device profile cannot be deleted!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_software_device")) { + throw new DataValidationException("The software referenced by the devices cannot be deleted!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_software_device_profile")) { + throw new DataValidationException("The software referenced by the device profile cannot be deleted!"); + } else { + throw t; + } + } + } + + @Override + public long sumDataSizeByTenantId(TenantId tenantId) { + return otaPackageDao.sumDataSizeByTenantId(tenantId); + } + + @Override + public void deleteOtaPackagesByTenantId(TenantId tenantId) { + log.trace("Executing deleteOtaPackagesByTenantId, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + 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 { + //TODO: validate url + } + } + + @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 (!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!"); + } + } + + 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!"); + } + } + + private PaginatedRemover tenantOtaPackageRemover = + new PaginatedRemover<>() { + + @Override + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { + return otaPackageInfoDao.findOtaPackageInfoByTenantId(id, pageLink); + } + + @Override + protected void removeEntity(TenantId tenantId, OtaPackageInfo entity) { + deleteOtaPackage(tenantId, entity.getId()); + } + }; + + protected Optional extractConstraintViolationException(Exception t) { + if (t instanceof ConstraintViolationException) { + return Optional.of((ConstraintViolationException) t); + } else if (t.getCause() instanceof ConstraintViolationException) { + return Optional.of((ConstraintViolationException) (t.getCause())); + } else { + return Optional.empty(); + } + } + + private static List toOtaPackageInfoKey(OtaPackageId otaPackageId) { + return Collections.singletonList(otaPackageId); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageDao.java b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageDao.java new file mode 100644 index 0000000000..ef8740030c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageDao.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.ota; + +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.TenantEntityDao; +import org.thingsboard.server.dao.TenantEntityWithDataDao; + +public interface OtaPackageDao extends Dao, TenantEntityWithDataDao { + Long sumDataSizeByTenantId(TenantId tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageInfoDao.java similarity index 55% rename from dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java rename to dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageInfoDao.java index 7cb6c3a57f..c40accf00a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageInfoDao.java @@ -13,25 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.firmware; +package org.thingsboard.server.dao.ota; -import org.thingsboard.server.common.data.FirmwareInfo; -import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.OtaPackageId; 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.Dao; -import java.util.UUID; +public interface OtaPackageInfoDao extends Dao { -public interface FirmwareInfoDao extends Dao { + PageData findOtaPackageInfoByTenantId(TenantId tenantId, PageLink pageLink); - PageData findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink); + PageData findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink); - PageData findFirmwareInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink); - - boolean isFirmwareUsed(FirmwareId firmwareId, FirmwareType type, DeviceProfileId deviceProfileId); + boolean isOtaPackageUsed(OtaPackageId otaPackageId, OtaPackageType otaPackageType, DeviceProfileId deviceProfileId); } 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 6dacd1357f..f442f5acea 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 @@ -19,6 +19,7 @@ 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.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; @@ -28,16 +29,19 @@ 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; @@ -49,12 +53,13 @@ public class BaseResourceService implements ResourceService { private final TbResourceDao resourceDao; private final TbResourceInfoDao resourceInfoDao; private final TenantDao tenantDao; + private final TbTenantProfileCache tenantProfileCache; - - public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) { + public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao, @Lazy TbTenantProfileCache tenantProfileCache) { this.resourceDao = resourceDao; this.resourceInfoDao = resourceInfoDao; this.tenantDao = tenantDao; + this.tenantProfileCache = tenantProfileCache; } @Override @@ -143,8 +148,23 @@ public class BaseResourceService implements ResourceService { tenantResourcesRemover.removeEntities(tenantId, tenantId); } + @Override + public long sumDataSizeByTenantId(TenantId tenantId) { + 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())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java index 230e104191..f0f3f3a1e5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java @@ -21,10 +21,11 @@ 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.Dao; +import org.thingsboard.server.dao.TenantEntityWithDataDao; import java.util.List; -public interface TbResourceDao extends Dao { +public interface TbResourceDao extends Dao, TenantEntityWithDataDao { TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java index ff8e79efc2..e630624ed4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java @@ -23,8 +23,10 @@ import org.hibernate.validator.cfg.ConstraintMapping; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.dao.TenantEntityDao; +import org.thingsboard.server.dao.TenantEntityWithDataDao; import org.thingsboard.server.dao.exception.DataValidationException; import javax.validation.ConstraintViolation; @@ -123,6 +125,19 @@ public abstract class DataValidator> { } } + protected void validateMaxSumDataSizePerTenant(TenantId tenantId, + TenantEntityWithDataDao dataDao, + long maxSumDataSize, + long currentDataSize, + EntityType entityType) { + if (maxSumDataSize > 0) { + if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) { + throw new DataValidationException(String.format("Failed to create the %s, files size limit is exhausted %d bytes!", + entityType.name().toLowerCase().replaceAll("_", " "), maxSumDataSize)); + } + } + } + protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) { Set expectedFields = new HashSet<>(); Iterator fieldsIterator = expectedNode.fieldNames(); 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 6bad1af0e5..18852cbf11 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 @@ -26,6 +26,7 @@ import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.BaseEntity; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -87,6 +88,12 @@ public abstract class JpaAbstractDao, D> return !getCrudRepository().existsById(id); } + @Transactional + public void removeAllByIds(Collection ids) { + CrudRepository repository = getCrudRepository(); + ids.forEach(repository::deleteById); + } + @Override public List find(TenantId tenantId) { List entities = Lists.newArrayList(getCrudRepository().findAll()); 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 b4c0ac09c6..8f862390c3 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,11 +17,13 @@ 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.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; @@ -159,4 +161,8 @@ public interface AlarmRepository extends CrudRepository { @Param("affectedEntityId") UUID affectedEntityId, @Param("affectedEntityType") String affectedEntityType, @Param("alarmStatuses") Set alarmStatuses); + + @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.createdTime < :time AND a.endTs < :time") + Page findAlarmsIdsByEndTsBeforeAndTenantId(@Param("time") Long time, @Param("tenantId") UUID tenantId, Pageable pageable); + } 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 cf222413b0..3216a23eee 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 @@ -26,10 +26,12 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; 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; import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.dao.DaoUtil; @@ -161,4 +163,10 @@ public class JpaAlarmDao extends JpaAbstractDao implements A public Set findAlarmSeverities(TenantId tenantId, EntityId entityId, Set statuses) { return alarmRepository.findAlarmSeverities(tenantId.getId(), entityId.getId(), entityId.getEntityType().name(), statuses); } + + @Override + public PageData findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink) { + return DaoUtil.pageToPageData(alarmRepository.findAlarmsIdsByEndTsBeforeAndTenantId(time, tenantId.getId(), DaoUtil.toPageable(pageLink))) + .mapData(AlarmId::new); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 3bbe18f2ab..58c05547b0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -96,23 +96,35 @@ public interface DeviceRepository extends PagingAndSortingRepository findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId, - @Param("type") String type, + @Param("deviceProfileId") UUID deviceProfileId, @Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + - "AND d.type = :type " + + "AND d.deviceProfileId = :deviceProfileId " + "AND d.softwareId = null " + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") Page findByTenantIdAndTypeAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId, - @Param("type") String type, + @Param("deviceProfileId") UUID deviceProfileId, @Param("textSearch") String textSearch, Pageable pageable); + @Query("SELECT count(*) FROM DeviceEntity d WHERE d.tenantId = :tenantId " + + "AND d.deviceProfileId = :deviceProfileId " + + "AND d.firmwareId = null") + Long countByTenantIdAndDeviceProfileIdAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId, + @Param("deviceProfileId") UUID deviceProfileId); + + @Query("SELECT count(*) FROM DeviceEntity d WHERE d.tenantId = :tenantId " + + "AND d.deviceProfileId = :deviceProfileId " + + "AND d.softwareId = null") + Long countByTenantIdAndDeviceProfileIdAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId, + @Param("deviceProfileId") UUID deviceProfileId); + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + 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 344286d7ac..0e3e843405 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 @@ -18,6 +18,8 @@ package org.thingsboard.server.dao.sql.device; import com.google.common.util.concurrent.ListenableFuture; 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.stereotype.Component; import org.springframework.util.StringUtils; @@ -27,6 +29,8 @@ 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.id.TenantId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUtil; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.DaoUtil; @@ -155,23 +159,27 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(UUID tenantId, String type, PageLink pageLink) { - return DaoUtil.toPageData( - deviceRepository.findByTenantIdAndTypeAndFirmwareIdIsNull( - tenantId, - type, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink))); + public PageData findDevicesByTenantIdAndTypeAndEmptyOtaPackage(UUID tenantId, + UUID deviceProfileId, + OtaPackageType type, + PageLink pageLink) { + Pageable pageable = DaoUtil.toPageable(pageLink); + String searchText = Objects.toString(pageLink.getTextSearch(), ""); + Page page = OtaPackageUtil.getByOtaPackageType( + () -> deviceRepository.findByTenantIdAndTypeAndFirmwareIdIsNull(tenantId, deviceProfileId, searchText, pageable), + () -> deviceRepository.findByTenantIdAndTypeAndSoftwareIdIsNull(tenantId, deviceProfileId, searchText, pageable), + type + ); + return DaoUtil.toPageData(page); } @Override - public PageData findDevicesByTenantIdAndTypeAndEmptySoftware(UUID tenantId, String type, PageLink pageLink) { - return DaoUtil.toPageData( - deviceRepository.findByTenantIdAndTypeAndSoftwareIdIsNull( - tenantId, - type, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink))); + public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(UUID tenantId, UUID deviceProfileId, OtaPackageType type) { + return OtaPackageUtil.getByOtaPackageType( + () -> deviceRepository.countByTenantIdAndDeviceProfileIdAndFirmwareIdIsNull(tenantId, deviceProfileId), + () -> deviceRepository.countByTenantIdAndDeviceProfileIdAndSoftwareIdIsNull(tenantId, deviceProfileId), + type + ); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java deleted file mode 100644 index dab6ce3304..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.firmware; - -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -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.firmware.FirmwareType; -import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; - -import java.util.UUID; - -public interface FirmwareInfoRepository extends CrudRepository { - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + - "f.tenantId = :tenantId " + - "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") - Page findAllByTenantId(@Param("tenantId") UUID tenantId, - @Param("searchText") String searchText, - Pageable pageable); - - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + - "f.tenantId = :tenantId " + - "AND f.deviceProfileId = :deviceProfileId " + - "AND f.type = :type " + - "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" + - "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") - Page findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(@Param("tenantId") UUID tenantId, - @Param("deviceProfileId") UUID deviceProfileId, - @Param("type") FirmwareType type, - @Param("hasData") boolean hasData, - @Param("searchText") String searchText, - Pageable pageable); - - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE f.id = :id") - FirmwareInfoEntity findFirmwareInfoById(@Param("id") UUID id); - - @Query(value = "SELECT exists(SELECT * " + - "FROM device_profile AS dp " + - "LEFT JOIN device AS d ON dp.id = d.device_profile_id " + - "WHERE dp.id = :deviceProfileId AND " + - "(('FIRMWARE' = :type AND (dp.firmware_id = :firmwareId OR d.firmware_id = :firmwareId)) " + - "OR ('SOFTWARE' = :type AND (dp.software_id = :firmwareId or d.software_id = :firmwareId))))", nativeQuery = true) - boolean isFirmwareUsed(@Param("firmwareId") UUID firmwareId, @Param("deviceProfileId") UUID deviceProfileId, @Param("type") String type); - -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java deleted file mode 100644 index 8cae33a057..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.firmware; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.repository.CrudRepository; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.FirmwareInfo; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.FirmwareId; -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.DaoUtil; -import org.thingsboard.server.dao.firmware.FirmwareInfoDao; -import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; -import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; - -import java.util.Objects; -import java.util.UUID; - -@Slf4j -@Component -public class JpaFirmwareInfoDao extends JpaAbstractSearchTextDao implements FirmwareInfoDao { - - @Autowired - private FirmwareInfoRepository firmwareInfoRepository; - - @Override - protected Class getEntityClass() { - return FirmwareInfoEntity.class; - } - - @Override - protected CrudRepository getCrudRepository() { - return firmwareInfoRepository; - } - - @Override - public FirmwareInfo findById(TenantId tenantId, UUID id) { - return DaoUtil.getData(firmwareInfoRepository.findFirmwareInfoById(id)); - } - - @Override - public FirmwareInfo save(TenantId tenantId, FirmwareInfo firmwareInfo) { - FirmwareInfo savedFirmware = super.save(tenantId, firmwareInfo); - if (firmwareInfo.getId() == null) { - return savedFirmware; - } else { - return findById(tenantId, savedFirmware.getId().getId()); - } - } - - @Override - public PageData findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink) { - return DaoUtil.toPageData(firmwareInfoRepository - .findAllByTenantId( - tenantId.getId(), - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink))); - } - - @Override - public PageData findFirmwareInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink) { - return DaoUtil.toPageData(firmwareInfoRepository - .findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData( - tenantId.getId(), - deviceProfileId.getId(), - firmwareType, - hasData, - Objects.toString(pageLink.getTextSearch(), ""), - DaoUtil.toPageable(pageLink))); - } - - @Override - public boolean isFirmwareUsed(FirmwareId firmwareId, FirmwareType type, DeviceProfileId deviceProfileId) { - return firmwareInfoRepository.isFirmwareUsed(firmwareId.getId(), deviceProfileId.getId(), type.name()); - } -} 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 new file mode 100644 index 0000000000..0d74821678 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2DomainDao.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.OAuth2Domain; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2DomainEntity; +import org.thingsboard.server.dao.oauth2.OAuth2DomainDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2DomainDao extends JpaAbstractDao implements OAuth2DomainDao { + + private final OAuth2DomainRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2DomainEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public List findByOAuth2ParamsId(UUID oauth2ParamsId) { + return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId)); + } + +} + 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 new file mode 100644 index 0000000000..6f216b23f2 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2MobileDao.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.OAuth2Mobile; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2MobileEntity; +import org.thingsboard.server.dao.oauth2.OAuth2MobileDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2MobileDao extends JpaAbstractDao implements OAuth2MobileDao { + + private final OAuth2MobileRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2MobileEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public List findByOAuth2ParamsId(UUID oauth2ParamsId) { + return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId)); + } + +} + 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 new file mode 100644 index 0000000000..2f28cb58f6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ParamsDao.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.OAuth2Params; +import org.thingsboard.server.dao.model.sql.OAuth2ParamsEntity; +import org.thingsboard.server.dao.oauth2.OAuth2ParamsDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2ParamsDao extends JpaAbstractDao implements OAuth2ParamsDao { + private final OAuth2ParamsRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2ParamsEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public void deleteAll() { + repository.deleteAll(); + } +} 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 new file mode 100644 index 0000000000..c3ee8a8c03 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2RegistrationDao.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.SchemeType; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.OAuth2RegistrationEntity; +import org.thingsboard.server.dao.oauth2.OAuth2RegistrationDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.List; +import java.util.UUID; + +@Component +@RequiredArgsConstructor +public class JpaOAuth2RegistrationDao extends JpaAbstractDao implements OAuth2RegistrationDao { + + private final OAuth2RegistrationRepository repository; + + @Override + protected Class getEntityClass() { + return OAuth2RegistrationEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return repository; + } + + @Override + public List findEnabledByDomainSchemesDomainNameAndPkgName(List domainSchemes, String domainName, String pkgName) { + return DaoUtil.convertDataList(repository.findAllEnabledByDomainSchemesNameAndPkgName(domainSchemes, domainName, pkgName)); + } + + @Override + public List findByOAuth2ParamsId(UUID oauth2ParamsId) { + return DaoUtil.convertDataList(repository.findByOauth2ParamsId(oauth2ParamsId)); + } + + @Override + public String findCallbackUrlScheme(UUID id, String pkgName) { + return repository.findCallbackUrlScheme(id, pkgName); + } + +} 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 new file mode 100644 index 0000000000..26d084f613 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2DomainRepository.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.OAuth2DomainEntity; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2DomainRepository extends CrudRepository { + + 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 new file mode 100644 index 0000000000..d4660ef2ee --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2MobileRepository.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.OAuth2MobileEntity; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2MobileRepository extends CrudRepository { + + List findByOauth2ParamsId(UUID oauth2ParamsId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java similarity index 77% rename from dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java index a969507798..8e53b34e31 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ParamsRepository.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.firmware; +package org.thingsboard.server.dao.sql.oauth2; import org.springframework.data.repository.CrudRepository; -import org.thingsboard.server.dao.model.sql.FirmwareEntity; +import org.thingsboard.server.dao.model.sql.OAuth2ParamsEntity; import java.util.UUID; -public interface FirmwareRepository extends CrudRepository { +public interface OAuth2ParamsRepository extends CrudRepository { } 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 new file mode 100644 index 0000000000..676df8adcc --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2RegistrationRepository.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.oauth2; + +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; + +import java.util.List; +import java.util.UUID; + +public interface OAuth2RegistrationRepository extends CrudRepository { + + @Query("SELECT reg " + + "FROM OAuth2RegistrationEntity reg " + + "LEFT JOIN OAuth2ParamsEntity params on reg.oauth2ParamsId = params.id " + + "LEFT JOIN OAuth2DomainEntity domain on reg.oauth2ParamsId = domain.oauth2ParamsId " + + "LEFT JOIN OAuth2MobileEntity mobile on reg.oauth2ParamsId = mobile.oauth2ParamsId " + + "WHERE params.enabled = true " + + "AND domain.domainName = :domainName " + + "AND domain.domainScheme IN (:domainSchemes) " + + "AND (:pkgName IS NULL OR mobile.pkgName = :pkgName)") + List findAllEnabledByDomainSchemesNameAndPkgName(@Param("domainSchemes") List domainSchemes, + @Param("domainName") String domainName, + @Param("pkgName") String pkgName); + + List findByOauth2ParamsId(UUID oauth2ParamsId); + + @Query("SELECT mobile.callbackUrlScheme " + + "FROM OAuth2MobileEntity mobile " + + "LEFT JOIN OAuth2RegistrationEntity reg on mobile.oauth2ParamsId = reg.oauth2ParamsId " + + "WHERE reg.id = :registrationId " + + "AND mobile.pkgName = :pkgName") + String findCallbackUrlScheme(@Param("registrationId") UUID id, + @Param("pkgName") String pkgName); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/JpaOAuth2ClientRegistrationDao.java similarity index 82% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/JpaOAuth2ClientRegistrationDao.java index 8aea9c9c3f..8dfe92b18d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/JpaOAuth2ClientRegistrationDao.java @@ -13,19 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.oauth2; +package org.thingsboard.server.dao.sql.oauth2.deprecated; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistration; -import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; -import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationDao; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistration; +import org.thingsboard.server.dao.model.sql.deprecated.OAuth2ClientRegistrationEntity; +import org.thingsboard.server.dao.oauth2.deprecated.OAuth2ClientRegistrationDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import java.util.UUID; +@Deprecated @Component @RequiredArgsConstructor public class JpaOAuth2ClientRegistrationDao extends JpaAbstractDao implements OAuth2ClientRegistrationDao { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/JpaOAuth2ClientRegistrationInfoDao.java similarity index 85% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/JpaOAuth2ClientRegistrationInfoDao.java index 0a25678e16..d37ca981f2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/JpaOAuth2ClientRegistrationInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/JpaOAuth2ClientRegistrationInfoDao.java @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.oauth2; +package org.thingsboard.server.dao.sql.oauth2.deprecated; import lombok.RequiredArgsConstructor; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.oauth2.ExtendedOAuth2ClientRegistrationInfo; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.ExtendedOAuth2ClientRegistrationInfo; +import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity; -import org.thingsboard.server.dao.oauth2.OAuth2ClientRegistrationInfoDao; +import org.thingsboard.server.dao.model.sql.deprecated.OAuth2ClientRegistrationInfoEntity; +import org.thingsboard.server.dao.oauth2.deprecated.OAuth2ClientRegistrationInfoDao; import org.thingsboard.server.dao.sql.JpaAbstractDao; import java.util.ArrayList; @@ -31,6 +31,7 @@ import java.util.List; import java.util.UUID; import java.util.stream.Collectors; +@Deprecated @Component @RequiredArgsConstructor public class JpaOAuth2ClientRegistrationInfoDao extends JpaAbstractDao implements OAuth2ClientRegistrationInfoDao { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/OAuth2ClientRegistrationInfoRepository.java similarity index 81% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/OAuth2ClientRegistrationInfoRepository.java index 18ac3be05a..73a2ca9bba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/OAuth2ClientRegistrationInfoRepository.java @@ -13,18 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.oauth2; +package org.thingsboard.server.dao.sql.oauth2.deprecated; 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.ExtendedOAuth2ClientRegistrationInfoEntity; -import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationInfoEntity; +import org.thingsboard.server.dao.model.sql.deprecated.ExtendedOAuth2ClientRegistrationInfoEntity; +import org.thingsboard.server.dao.model.sql.deprecated.OAuth2ClientRegistrationInfoEntity; import java.util.List; import java.util.UUID; +@Deprecated public interface OAuth2ClientRegistrationInfoRepository extends CrudRepository { @Query("SELECT new OAuth2ClientRegistrationInfoEntity(cr_info) " + "FROM OAuth2ClientRegistrationInfoEntity cr_info " + @@ -34,7 +35,7 @@ public interface OAuth2ClientRegistrationInfoRepository extends CrudRepository findAllByDomainSchemesAndName(@Param("domainSchemes") List domainSchemes, @Param("domainName") String domainName); - @Query("SELECT new org.thingsboard.server.dao.model.sql.ExtendedOAuth2ClientRegistrationInfoEntity(cr_info, cr.domainName, cr.domainScheme) " + + @Query("SELECT new org.thingsboard.server.dao.model.sql.deprecated.ExtendedOAuth2ClientRegistrationInfoEntity(cr_info, cr.domainName, cr.domainScheme) " + "FROM OAuth2ClientRegistrationInfoEntity cr_info " + "LEFT JOIN OAuth2ClientRegistrationEntity cr on cr_info.id = cr.clientRegistrationInfoId ") List findAllExtended(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/OAuth2ClientRegistrationRepository.java similarity index 83% rename from dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/OAuth2ClientRegistrationRepository.java index 1849f46514..1f4e4783ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/OAuth2ClientRegistrationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/oauth2/deprecated/OAuth2ClientRegistrationRepository.java @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.oauth2; +package org.thingsboard.server.dao.sql.oauth2.deprecated; import org.springframework.data.repository.CrudRepository; -import org.thingsboard.server.dao.model.sql.OAuth2ClientRegistrationEntity; +import org.thingsboard.server.dao.model.sql.deprecated.OAuth2ClientRegistrationEntity; import java.util.UUID; +@Deprecated public interface OAuth2ClientRegistrationRepository extends CrudRepository { } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java similarity index 55% rename from dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java index 44b956c298..98309b9e51 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java @@ -13,34 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.sql.firmware; +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.stereotype.Component; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.dao.firmware.FirmwareDao; -import org.thingsboard.server.dao.model.sql.FirmwareEntity; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.ota.OtaPackageDao; +import org.thingsboard.server.dao.model.sql.OtaPackageEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.UUID; @Slf4j @Component -public class JpaFirmwareDao extends JpaAbstractSearchTextDao implements FirmwareDao { +public class JpaOtaPackageDao extends JpaAbstractSearchTextDao implements OtaPackageDao { @Autowired - private FirmwareRepository firmwareRepository; + private OtaPackageRepository otaPackageRepository; @Override - protected Class getEntityClass() { - return FirmwareEntity.class; + protected Class getEntityClass() { + return OtaPackageEntity.class; } @Override - protected CrudRepository getCrudRepository() { - return firmwareRepository; + protected CrudRepository getCrudRepository() { + return otaPackageRepository; } + @Override + public Long sumDataSizeByTenantId(TenantId tenantId) { + return otaPackageRepository.sumDataSizeByTenantId(tenantId.getId()); + } } 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 new file mode 100644 index 0000000000..f531c7fd4a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.ota; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.OtaPackageId; +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.DaoUtil; +import org.thingsboard.server.dao.ota.OtaPackageInfoDao; +import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity; +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; + +import java.util.Objects; +import java.util.UUID; + +@Slf4j +@Component +public class JpaOtaPackageInfoDao extends JpaAbstractSearchTextDao implements OtaPackageInfoDao { + + @Autowired + private OtaPackageInfoRepository otaPackageInfoRepository; + + @Override + protected Class getEntityClass() { + return OtaPackageInfoEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return otaPackageInfoRepository; + } + + @Override + public OtaPackageInfo findById(TenantId tenantId, UUID id) { + return DaoUtil.getData(otaPackageInfoRepository.findOtaPackageInfoById(id)); + } + + @Override + public OtaPackageInfo save(TenantId tenantId, OtaPackageInfo otaPackageInfo) { + OtaPackageInfo savedOtaPackage = super.save(tenantId, otaPackageInfo); + if (otaPackageInfo.getId() == null) { + return savedOtaPackage; + } else { + return findById(tenantId, savedOtaPackage.getId().getId()); + } + } + + @Override + public PageData findOtaPackageInfoByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(otaPackageInfoRepository + .findAllByTenantId( + tenantId.getId(), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } + + @Override + public PageData findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) { + return DaoUtil.toPageData(otaPackageInfoRepository + .findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData( + tenantId.getId(), + deviceProfileId.getId(), + otaPackageType, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } + + @Override + public boolean isOtaPackageUsed(OtaPackageId otaPackageId, OtaPackageType otaPackageType, DeviceProfileId deviceProfileId) { + return otaPackageInfoRepository.isOtaPackageUsed(otaPackageId.getId(), deviceProfileId.getId(), otaPackageType.name()); + } +} 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 new file mode 100644 index 0000000000..b380f8a150 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.ota; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +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 { + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, 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, '%'))") + Page findAllByTenantId(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, + Pageable pageable); + + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, true) FROM OtaPackageEntity f WHERE " + + "f.tenantId = :tenantId " + + "AND f.deviceProfileId = :deviceProfileId " + + "AND f.type = :type " + + "AND (f.data IS NOT NULL OR f.url IS NOT NULL) " + + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(@Param("tenantId") UUID tenantId, + @Param("deviceProfileId") UUID deviceProfileId, + @Param("type") OtaPackageType type, + @Param("searchText") String searchText, + Pageable pageable); + + @Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, 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.id = :id") + OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id); + + @Query(value = "SELECT exists(SELECT * " + + "FROM device_profile AS dp " + + "LEFT JOIN device AS d ON dp.id = d.device_profile_id " + + "WHERE dp.id = :deviceProfileId AND " + + "(('FIRMWARE' = :type AND (dp.firmware_id = :otaPackageId OR d.firmware_id = :otaPackageId)) " + + "OR ('SOFTWARE' = :type AND (dp.software_id = :otaPackageId or d.software_id = :otaPackageId))))", nativeQuery = true) + boolean isOtaPackageUsed(@Param("otaPackageId") UUID otaPackageId, @Param("deviceProfileId") UUID deviceProfileId, @Param("type") String type); + +} 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 new file mode 100644 index 0000000000..47e6f82285 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageRepository.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.ota; + +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 { + @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/resource/JpaTbResourceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java index f35f654f77..324549765e 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 @@ -92,4 +92,8 @@ public class JpaTbResourceDao extends JpaAbstractSearchTextDao Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink, TenantInfoEntity.tenantInfoColumnMap))); } + + @Override + public PageData findTenantsIds(PageLink pageLink) { + return DaoUtil.pageToPageData(tenantRepository.findTenantsIds(DaoUtil.toPageable(pageLink))).mapData(TenantId::new); + } + } 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 b43d70197c..8ab12e0bb5 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 @@ -50,4 +50,8 @@ public interface TenantRepository extends PagingAndSortingRepository findTenantInfoByRegionNextPage(@Param("region") String region, @Param("textSearch") String textSearch, Pageable pageable); + + @Query("SELECT t.id FROM TenantEntity t") + Page findTenantsIds(Pageable pageable); + } 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 bff1c2c8a9..5bceb35376 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 @@ -46,5 +46,7 @@ public interface TenantDao extends Dao { PageData findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink); PageData findTenantInfosByRegion(TenantId tenantId, String region, PageLink pageLink); - + + PageData findTenantsIds(PageLink pageLink); + } 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 6391ed73e7..c5050467dd 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 @@ -35,7 +35,7 @@ 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.firmware.FirmwareService; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; @@ -94,7 +94,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe private ResourceService resourceService; @Autowired - private FirmwareService firmwareService; + private OtaPackageService otaPackageService; @Override public Tenant findTenantById(TenantId tenantId) { @@ -150,7 +150,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe ruleChainService.deleteRuleChainsByTenantId(tenantId); apiUsageStateService.deleteApiUsageStateByTenantId(tenantId); resourceService.deleteResourcesByTenantId(tenantId); - firmwareService.deleteFirmwaresByTenantId(tenantId); + otaPackageService.deleteOtaPackagesByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index fcb7f82ed0..0e7aec5c19 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -160,14 +160,15 @@ CREATE TABLE IF NOT EXISTS rule_node_state ( CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS firmware ( - id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS ota_package ( + id uuid NOT NULL CONSTRAINT ota_package_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, device_profile_id uuid , type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, + url varchar(255), file_name varchar(255), content_type varchar(255), checksum_algorithm varchar(32), @@ -176,7 +177,7 @@ CREATE TABLE IF NOT EXISTS firmware ( data_size bigint, additional_info varchar, search_text varchar(255), - CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) + CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) ); CREATE TABLE IF NOT EXISTS device_profile ( @@ -202,8 +203,8 @@ CREATE TABLE IF NOT EXISTS device_profile ( CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id), - CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id), - CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES ota_package(id), + CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id) ); CREATE TABLE IF NOT EXISTS device ( @@ -222,8 +223,8 @@ CREATE TABLE IF NOT EXISTS device ( software_id uuid, CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id), - CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id), - CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES ota_package(id), + CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES ota_package(id) ); CREATE TABLE IF NOT EXISTS device_credentials ( @@ -373,9 +374,16 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary ( CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_params ( + id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY, enabled boolean, + tenant_id uuid, + created_time bigint NOT NULL +); + +CREATE TABLE IF NOT EXISTS oauth2_registration ( + id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, created_time bigint NOT NULL, additional_info varchar, client_id varchar(255), @@ -403,15 +411,28 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( custom_url varchar(255), custom_username varchar(255), custom_password varchar(255), - custom_send_token boolean + custom_send_token boolean, + CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_domain ( + id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, created_time bigint NOT NULL, domain_name varchar(255), domain_scheme varchar(31), - client_registration_info_id uuid + CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, + CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme) +); + +CREATE TABLE IF NOT EXISTS oauth2_mobile ( + id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, + created_time bigint NOT NULL, + pkg_name varchar(255), + callback_url_scheme varchar(255), + CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, + CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name) ); CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( @@ -442,6 +463,49 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); +-- Deprecated +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, + enabled boolean, + created_time bigint NOT NULL, + additional_info varchar, + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + custom_send_token boolean +); + +-- Deprecated +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + CREATE TABLE IF NOT EXISTS api_usage_state ( id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, created_time bigint NOT NULL, diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 30b9a3dbe7..377d140597 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -178,14 +178,15 @@ CREATE TABLE IF NOT EXISTS rule_node_state ( CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS firmware ( - id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS ota_package ( + id uuid NOT NULL CONSTRAINT ota_package_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, device_profile_id uuid , type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, + url varchar(255), file_name varchar(255), content_type varchar(255), checksum_algorithm varchar(32), @@ -194,7 +195,7 @@ CREATE TABLE IF NOT EXISTS firmware ( data_size bigint, additional_info varchar, search_text varchar(255), - CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) + 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 ); @@ -221,8 +222,8 @@ CREATE TABLE IF NOT EXISTS device_profile ( CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id), - CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id), - CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES ota_package(id), + CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id) ); -- We will use one-to-many relation in the first release and extend this feature in case of user requests @@ -250,8 +251,8 @@ CREATE TABLE IF NOT EXISTS device ( software_id uuid, CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id), - CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id), - CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES ota_package(id), + CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES ota_package(id) ); CREATE TABLE IF NOT EXISTS device_credentials ( @@ -410,9 +411,16 @@ CREATE TABLE IF NOT EXISTS ts_kv_dictionary CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_params ( + id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY, enabled boolean, + tenant_id uuid, + created_time bigint NOT NULL +); + +CREATE TABLE IF NOT EXISTS oauth2_registration ( + id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, created_time bigint NOT NULL, additional_info varchar, client_id varchar(255), @@ -440,15 +448,28 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( custom_url varchar(255), custom_username varchar(255), custom_password varchar(255), - custom_send_token boolean + custom_send_token boolean, + CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE ); -CREATE TABLE IF NOT EXISTS oauth2_client_registration ( - id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, +CREATE TABLE IF NOT EXISTS oauth2_domain ( + id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, created_time bigint NOT NULL, domain_name varchar(255), domain_scheme varchar(31), - client_registration_info_id uuid + CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, + CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme) +); + +CREATE TABLE IF NOT EXISTS oauth2_mobile ( + id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY, + oauth2_params_id uuid NOT NULL, + created_time bigint NOT NULL, + pkg_name varchar(255), + callback_url_scheme varchar(255), + CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE, + CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name) ); CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( @@ -479,6 +500,49 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); +-- Deprecated +CREATE TABLE IF NOT EXISTS oauth2_client_registration_info ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY, + enabled boolean, + created_time bigint NOT NULL, + additional_info varchar, + client_id varchar(255), + client_secret varchar(255), + authorization_uri varchar(255), + token_uri varchar(255), + scope varchar(255), + user_info_uri varchar(255), + user_name_attribute_name varchar(255), + jwk_set_uri varchar(255), + client_authentication_method varchar(255), + login_button_label varchar(255), + login_button_icon varchar(255), + allow_user_creation boolean, + activate_user boolean, + type varchar(31), + basic_email_attribute_key varchar(31), + basic_first_name_attribute_key varchar(31), + basic_last_name_attribute_key varchar(31), + basic_tenant_name_strategy varchar(31), + basic_tenant_name_pattern varchar(255), + basic_customer_name_pattern varchar(255), + basic_default_dashboard_name varchar(255), + basic_always_full_screen boolean, + custom_url varchar(255), + custom_username varchar(255), + custom_password varchar(255), + custom_send_token boolean +); + +-- Deprecated +CREATE TABLE IF NOT EXISTS oauth2_client_registration ( + id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY, + created_time bigint NOT NULL, + domain_name varchar(255), + domain_scheme varchar(31), + client_registration_info_id uuid +); + CREATE TABLE IF NOT EXISTS api_usage_state ( id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, created_time bigint NOT NULL, 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 091ab1741e..e01c51e8ad 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 @@ -54,7 +54,7 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; -import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -158,9 +158,8 @@ public abstract class AbstractServiceTest { @Autowired protected ResourceService resourceService; - @Autowired - protected FirmwareService firmwareService; + protected OtaPackageService otaPackageService; public class IdComparator implements Comparator { @Override 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 5516cb6181..3f9f5edc36 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 @@ -28,10 +28,9 @@ 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.DeviceTransportType; -import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.firmware.FirmwareType; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -45,7 +44,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @@ -99,7 +98,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault()); Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId()); - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(savedDeviceProfile.getId()); firmware.setType(FIRMWARE); @@ -110,7 +109,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); firmware.setData(ByteBuffer.wrap(new byte[]{1})); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + firmware.setDataSize(1L); + OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware); deviceProfile.setFirmwareId(savedFirmware.getId()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index 587fce1ed5..f1f081ad2c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -28,10 +28,10 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; -import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -46,7 +46,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; public abstract class BaseDeviceServiceTest extends AbstractServiceTest { @@ -189,7 +189,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertEquals(20, deviceCredentials.getCredentialsId().length()); - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(device.getDeviceProfileId()); firmware.setType(FIRMWARE); @@ -200,7 +200,8 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); firmware.setData(ByteBuffer.wrap(new byte[]{1})); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + firmware.setDataSize(1L); + OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware); savedDevice.setFirmwareId(savedFirmware.getId()); @@ -223,7 +224,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { DeviceProfile savedProfile = deviceProfileService.saveDeviceProfile(deviceProfile); Assert.assertNotNull(savedProfile); - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(savedProfile.getId()); firmware.setType(FIRMWARE); @@ -234,7 +235,8 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); firmware.setData(ByteBuffer.wrap(new byte[]{1})); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + firmware.setDataSize(1L); + OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware); savedDevice.setFirmwareId(savedFirmware.getId()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java index ebfbfccd2a..fa79e32bea 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java @@ -16,208 +16,225 @@ package org.thingsboard.server.dao.service; import com.google.common.collect.Lists; -import com.google.common.collect.Sets; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.server.common.data.oauth2.*; +import org.thingsboard.server.common.data.oauth2.MapperType; +import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2CustomMapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2DomainInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2MobileInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2ParamsInfo; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.common.data.oauth2.OAuth2RegistrationInfo; +import org.thingsboard.server.common.data.oauth2.SchemeType; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2Service; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.UUID; import java.util.stream.Collectors; public class BaseOAuth2ServiceTest extends AbstractServiceTest { - private static final OAuth2ClientsParams EMPTY_PARAMS = new OAuth2ClientsParams(false, new ArrayList<>()); + private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, Collections.emptyList()); @Autowired protected OAuth2Service oAuth2Service; @Before public void beforeRun() { - Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty()); + Assert.assertTrue(oAuth2Service.findAllRegistrations().isEmpty()); } @After public void after() { - oAuth2Service.saveOAuth2Params(EMPTY_PARAMS); - Assert.assertTrue(oAuth2Service.findAllClientRegistrationInfos().isEmpty()); - Assert.assertTrue(oAuth2Service.findOAuth2Params().getDomainsParams().isEmpty()); + oAuth2Service.saveOAuth2Info(EMPTY_PARAMS); + Assert.assertTrue(oAuth2Service.findAllRegistrations().isEmpty()); + Assert.assertTrue(oAuth2Service.findOAuth2Info().getOauth2ParamsInfos().isEmpty()); } @Test(expected = DataValidationException.class) public void testSaveHttpAndMixedDomainsTogether() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() )) .build() )); - oAuth2Service.saveOAuth2Params(clientsParams); + oAuth2Service.saveOAuth2Info(oAuth2Info); } @Test(expected = DataValidationException.class) public void testSaveHttpsAndMixedDomainsTogether() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), - DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build(), + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() )) .build() )); - oAuth2Service.saveOAuth2Params(clientsParams); + oAuth2Service.saveOAuth2Info(oAuth2Info); } @Test public void testCreateAndFindParams() { - OAuth2ClientsParams clientsParams = createDefaultClientsParams(); - oAuth2Service.saveOAuth2Params(clientsParams); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertNotNull(foundClientsParams); + OAuth2Info oAuth2Info = createDefaultOAuth2Info(); + oAuth2Service.saveOAuth2Info(oAuth2Info); + OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertNotNull(foundOAuth2Info); // TODO ask if it's safe to check equality on AdditionalProperties - Assert.assertEquals(clientsParams, foundClientsParams); + Assert.assertEquals(oAuth2Info, foundOAuth2Info); } @Test public void testDisableParams() { - OAuth2ClientsParams clientsParams = createDefaultClientsParams(); - clientsParams.setEnabled(true); - oAuth2Service.saveOAuth2Params(clientsParams); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertNotNull(foundClientsParams); - Assert.assertEquals(clientsParams, foundClientsParams); - - clientsParams.setEnabled(false); - oAuth2Service.saveOAuth2Params(clientsParams); - OAuth2ClientsParams foundDisabledClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertEquals(clientsParams, foundDisabledClientsParams); + OAuth2Info oAuth2Info = createDefaultOAuth2Info(); + oAuth2Info.setEnabled(true); + oAuth2Service.saveOAuth2Info(oAuth2Info); + OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertNotNull(foundOAuth2Info); + Assert.assertEquals(oAuth2Info, foundOAuth2Info); + + oAuth2Info.setEnabled(false); + oAuth2Service.saveOAuth2Info(oAuth2Info); + OAuth2Info foundDisabledOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertEquals(oAuth2Info, foundDisabledOAuth2Info); } @Test public void testClearDomainParams() { - OAuth2ClientsParams clientsParams = createDefaultClientsParams(); - oAuth2Service.saveOAuth2Params(clientsParams); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertNotNull(foundClientsParams); - Assert.assertEquals(clientsParams, foundClientsParams); - - oAuth2Service.saveOAuth2Params(EMPTY_PARAMS); - OAuth2ClientsParams foundAfterClearClientsParams = oAuth2Service.findOAuth2Params(); + OAuth2Info oAuth2Info = createDefaultOAuth2Info(); + oAuth2Service.saveOAuth2Info(oAuth2Info); + OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertNotNull(foundOAuth2Info); + Assert.assertEquals(oAuth2Info, foundOAuth2Info); + + oAuth2Service.saveOAuth2Info(EMPTY_PARAMS); + OAuth2Info foundAfterClearClientsParams = oAuth2Service.findOAuth2Info(); Assert.assertNotNull(foundAfterClearClientsParams); Assert.assertEquals(EMPTY_PARAMS, foundAfterClearClientsParams); } @Test public void testUpdateClientsParams() { - OAuth2ClientsParams clientsParams = createDefaultClientsParams(); - oAuth2Service.saveOAuth2Params(clientsParams); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertNotNull(foundClientsParams); - Assert.assertEquals(clientsParams, foundClientsParams); - - OAuth2ClientsParams newClientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + OAuth2Info oAuth2Info = createDefaultOAuth2Info(); + oAuth2Service.saveOAuth2Info(oAuth2Info); + OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertNotNull(foundOAuth2Info); + Assert.assertEquals(oAuth2Info, foundOAuth2Info); + + OAuth2Info newOAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("another-domain").scheme(SchemeType.HTTPS).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto() + validRegistrationInfo() )) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() + OAuth2DomainInfo.builder().name("test-domain").scheme(SchemeType.MIXED).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto() + validRegistrationInfo() )) .build() )); - oAuth2Service.saveOAuth2Params(newClientsParams); - OAuth2ClientsParams foundAfterUpdateClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertNotNull(foundAfterUpdateClientsParams); - Assert.assertEquals(newClientsParams, foundAfterUpdateClientsParams); + oAuth2Service.saveOAuth2Info(newOAuth2Info); + OAuth2Info foundAfterUpdateOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertNotNull(foundAfterUpdateOAuth2Info); + Assert.assertEquals(newOAuth2Info, foundAfterUpdateOAuth2Info); } @Test public void testGetOAuth2Clients() { - List firstGroup = Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + List firstGroup = Lists.newArrayList( + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() ); - List secondGroup = Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto() + List secondGroup = Lists.newArrayList( + validRegistrationInfo(), + validRegistrationInfo() ); - List thirdGroup = Lists.newArrayList( - validClientRegistrationDto() + List thirdGroup = Lists.newArrayList( + validRegistrationInfo() ); - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(firstGroup) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(secondGroup) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), - DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(thirdGroup) .build() )); - oAuth2Service.saveOAuth2Params(clientsParams); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertNotNull(foundClientsParams); - Assert.assertEquals(clientsParams, foundClientsParams); + oAuth2Service.saveOAuth2Info(oAuth2Info); + OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertNotNull(foundOAuth2Info); + Assert.assertEquals(oAuth2Info, foundOAuth2Info); List firstGroupClientInfos = firstGroup.stream() - .map(clientRegistrationDto -> new OAuth2ClientInfo( - clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .map(registrationInfo -> new OAuth2ClientInfo( + registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) .collect(Collectors.toList()); List secondGroupClientInfos = secondGroup.stream() - .map(clientRegistrationDto -> new OAuth2ClientInfo( - clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .map(registrationInfo -> new OAuth2ClientInfo( + registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) .collect(Collectors.toList()); List thirdGroupClientInfos = thirdGroup.stream() - .map(clientRegistrationDto -> new OAuth2ClientInfo( - clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .map(registrationInfo -> new OAuth2ClientInfo( + registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) .collect(Collectors.toList()); - List nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain"); + List nonExistentDomainClients = oAuth2Service.getOAuth2Clients("http", "non-existent-domain", null); Assert.assertTrue(nonExistentDomainClients.isEmpty()); - List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain"); + List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null); Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( @@ -227,10 +244,10 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { ); }); - List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain"); + List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null); Assert.assertTrue(firstDomainHttpsClients.isEmpty()); - List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain"); + List fourthDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "fourth-domain", null); Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpClients.size()); secondGroupClientInfos.forEach(secondGroupClientInfo -> { Assert.assertTrue( @@ -239,7 +256,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { && clientInfo.getName().equals(secondGroupClientInfo.getName())) ); }); - List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain"); + List fourthDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "fourth-domain", null); Assert.assertEquals(secondGroupClientInfos.size(), fourthDomainHttpsClients.size()); secondGroupClientInfos.forEach(secondGroupClientInfo -> { Assert.assertTrue( @@ -249,7 +266,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { ); }); - List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null); Assert.assertEquals(firstGroupClientInfos.size() + secondGroupClientInfos.size(), secondDomainHttpClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( @@ -266,7 +283,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { ); }); - List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain"); + List secondDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "second-domain", null); Assert.assertEquals(firstGroupClientInfos.size() + thirdGroupClientInfos.size(), secondDomainHttpsClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( @@ -286,34 +303,35 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetOAuth2ClientsForHttpAndHttps() { - List firstGroup = Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + List firstGroup = Lists.newArrayList( + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() ); - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTPS).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(firstGroup) .build() )); - oAuth2Service.saveOAuth2Params(clientsParams); - OAuth2ClientsParams foundClientsParams = oAuth2Service.findOAuth2Params(); - Assert.assertNotNull(foundClientsParams); - Assert.assertEquals(clientsParams, foundClientsParams); + oAuth2Service.saveOAuth2Info(oAuth2Info); + OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertNotNull(foundOAuth2Info); + Assert.assertEquals(oAuth2Info, foundOAuth2Info); List firstGroupClientInfos = firstGroup.stream() - .map(clientRegistrationDto -> new OAuth2ClientInfo( - clientRegistrationDto.getLoginButtonLabel(), clientRegistrationDto.getLoginButtonIcon(), null)) + .map(registrationInfo -> new OAuth2ClientInfo( + registrationInfo.getLoginButtonLabel(), registrationInfo.getLoginButtonIcon(), null)) .collect(Collectors.toList()); - List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain"); + List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", null); Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( @@ -323,7 +341,7 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { ); }); - List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain"); + List firstDomainHttpsClients = oAuth2Service.getOAuth2Clients("https", "first-domain", null); Assert.assertEquals(firstGroupClientInfos.size(), firstDomainHttpsClients.size()); firstGroupClientInfos.forEach(firstGroupClientInfo -> { Assert.assertTrue( @@ -336,166 +354,220 @@ public class BaseOAuth2ServiceTest extends AbstractServiceTest { @Test public void testGetDisabledOAuth2Clients() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() )) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo() )) .build() )); - oAuth2Service.saveOAuth2Params(clientsParams); + oAuth2Service.saveOAuth2Info(oAuth2Info); - List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + List secondDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null); Assert.assertEquals(5, secondDomainHttpClients.size()); - clientsParams.setEnabled(false); - oAuth2Service.saveOAuth2Params(clientsParams); + oAuth2Info.setEnabled(false); + oAuth2Service.saveOAuth2Info(oAuth2Info); - List secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain"); + List secondDomainHttpDisabledClients = oAuth2Service.getOAuth2Clients("http", "second-domain", null); Assert.assertEquals(0, secondDomainHttpDisabledClients.size()); } @Test - public void testFindAllClientRegistrationInfos() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + public void testFindAllRegistrations() { + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() )) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo() )) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), - DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto() + validRegistrationInfo() )) .build() )); - oAuth2Service.saveOAuth2Params(clientsParams); - List foundClientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos(); - Assert.assertEquals(6, foundClientRegistrationInfos.size()); - clientsParams.getDomainsParams().stream() - .flatMap(domainParams -> domainParams.getClientRegistrations().stream()) - .forEach(clientRegistrationDto -> + oAuth2Service.saveOAuth2Info(oAuth2Info); + List foundRegistrations = oAuth2Service.findAllRegistrations(); + Assert.assertEquals(6, foundRegistrations.size()); + oAuth2Info.getOauth2ParamsInfos().stream() + .flatMap(paramsInfo -> paramsInfo.getClientRegistrations().stream()) + .forEach(registrationInfo -> Assert.assertTrue( - foundClientRegistrationInfos.stream() - .anyMatch(clientRegistrationInfo -> clientRegistrationInfo.getClientId().equals(clientRegistrationDto.getClientId())) + foundRegistrations.stream() + .anyMatch(registration -> registration.getClientId().equals(registrationInfo.getClientId())) ) ); } @Test - public void testFindClientRegistrationById() { - OAuth2ClientsParams clientsParams = new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + public void testFindRegistrationById() { + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() )) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo() )) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), - DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTPS).build(), + OAuth2DomainInfo.builder().name("fifth-domain").scheme(SchemeType.HTTP).build() )) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto() + validRegistrationInfo() )) .build() )); - oAuth2Service.saveOAuth2Params(clientsParams); - List clientRegistrationInfos = oAuth2Service.findAllClientRegistrationInfos(); - clientRegistrationInfos.forEach(clientRegistrationInfo -> { - OAuth2ClientRegistrationInfo foundClientRegistrationInfo = oAuth2Service.findClientRegistrationInfo(clientRegistrationInfo.getUuidId()); - Assert.assertEquals(clientRegistrationInfo, foundClientRegistrationInfo); + oAuth2Service.saveOAuth2Info(oAuth2Info); + List foundRegistrations = oAuth2Service.findAllRegistrations(); + foundRegistrations.forEach(registration -> { + OAuth2Registration foundRegistration = oAuth2Service.findRegistration(registration.getUuidId()); + Assert.assertEquals(registration, foundRegistration); }); } - private OAuth2ClientsParams createDefaultClientsParams() { - return new OAuth2ClientsParams(true, Lists.newArrayList( - OAuth2ClientsDomainParams.builder() + @Test + public void testFindCallbackUrlScheme() { + OAuth2Info oAuth2Info = new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() + .domainInfos(Lists.newArrayList( + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + )) + .mobileInfos(Lists.newArrayList( + OAuth2MobileInfo.builder().pkgName("com.test.pkg1").callbackUrlScheme("testPkg1Callback").build(), + OAuth2MobileInfo.builder().pkgName("com.test.pkg2").callbackUrlScheme("testPkg2Callback").build() + )) + .clientRegistrations(Lists.newArrayList( + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() + )) + .build(), + OAuth2ParamsInfo.builder() + .domainInfos(Lists.newArrayList( + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + )) + .mobileInfos(Collections.emptyList()) + .clientRegistrations(Lists.newArrayList( + validRegistrationInfo(), + validRegistrationInfo() + )) + .build() + )); + oAuth2Service.saveOAuth2Info(oAuth2Info); + + OAuth2Info foundOAuth2Info = oAuth2Service.findOAuth2Info(); + Assert.assertEquals(oAuth2Info, foundOAuth2Info); + + List firstDomainHttpClients = oAuth2Service.getOAuth2Clients("http", "first-domain", "com.test.pkg1"); + Assert.assertEquals(3, firstDomainHttpClients.size()); + for (OAuth2ClientInfo clientInfo : firstDomainHttpClients) { + String[] segments = clientInfo.getUrl().split("/"); + String registrationId = segments[segments.length-1]; + String callbackUrlScheme = oAuth2Service.findCallbackUrlScheme(UUID.fromString(registrationId), "com.test.pkg1"); + Assert.assertNotNull(callbackUrlScheme); + Assert.assertEquals("testPkg1Callback", callbackUrlScheme); + callbackUrlScheme = oAuth2Service.findCallbackUrlScheme(UUID.fromString(registrationId), "com.test.pkg2"); + Assert.assertNotNull(callbackUrlScheme); + Assert.assertEquals("testPkg2Callback", callbackUrlScheme); + callbackUrlScheme = oAuth2Service.findCallbackUrlScheme(UUID.fromString(registrationId), "com.test.pkg3"); + Assert.assertNull(callbackUrlScheme); + } + } + + private OAuth2Info createDefaultOAuth2Info() { + return new OAuth2Info(true, Lists.newArrayList( + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), - DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() + OAuth2DomainInfo.builder().name("first-domain").scheme(SchemeType.HTTP).build(), + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("third-domain").scheme(SchemeType.HTTPS).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo(), + validRegistrationInfo() )) .build(), - OAuth2ClientsDomainParams.builder() + OAuth2ParamsInfo.builder() .domainInfos(Lists.newArrayList( - DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), - DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() + OAuth2DomainInfo.builder().name("second-domain").scheme(SchemeType.MIXED).build(), + OAuth2DomainInfo.builder().name("fourth-domain").scheme(SchemeType.MIXED).build() )) + .mobileInfos(Collections.emptyList()) .clientRegistrations(Lists.newArrayList( - validClientRegistrationDto(), - validClientRegistrationDto() + validRegistrationInfo(), + validRegistrationInfo() )) .build() )); } - private ClientRegistrationDto validClientRegistrationDto() { - return ClientRegistrationDto.builder() + private OAuth2RegistrationInfo validRegistrationInfo() { + return OAuth2RegistrationInfo.builder() .clientId(UUID.randomUUID().toString()) .clientSecret(UUID.randomUUID().toString()) .authorizationUri(UUID.randomUUID().toString()) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java similarity index 55% rename from dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java index bd9e0ed372..35063eef75 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java @@ -25,14 +25,16 @@ import org.junit.rules.ExpectedException; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.Firmware; -import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; 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 java.nio.ByteBuffer; @@ -40,9 +42,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; -public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { +public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { public static final String TITLE = "My firmware"; private static final String FILE_NAME = "filename.txt"; @@ -50,9 +52,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { private static final String CONTENT_TYPE = "text/plain"; private static final ChecksumAlgorithm CHECKSUM_ALGORITHM = ChecksumAlgorithm.SHA256; private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; - private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1}); + private static final long DATA_SIZE = 1L; + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{(int) DATA_SIZE}); + private static final String URL = "http://firmware.test.org"; - private IdComparator idComparator = new IdComparator<>(); + private IdComparator idComparator = new IdComparator<>(); private TenantId tenantId; @@ -78,11 +82,46 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { @After public void after() { tenantService.deleteTenant(tenantId); + tenantProfileService.deleteTenantProfiles(tenantId); + } + + @Test + public void testSaveOtaPackageWithMaxSumDataSizeOutOfLimit() { + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId); + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxOtaPackagesInBytes(DATA_SIZE).build()); + tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile); + + Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId)); + + createFirmware(tenantId, "1"); + Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId)); + + thrown.expect(DataValidationException.class); + thrown.expectMessage(String.format("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE)); + createFirmware(tenantId, "2"); + } + + @Test + public void sumDataSizeByTenantId() { + Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId)); + + createFirmware(tenantId, "0.1"); + Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId)); + + int maxSumDataSize = 8; + List packages = new ArrayList<>(maxSumDataSize); + + for (int i = 2; i <= maxSumDataSize; i++) { + packages.add(createFirmware(tenantId, "0." + i)); + Assert.assertEquals(i, otaPackageService.sumDataSizeByTenantId(tenantId)); + } + + Assert.assertEquals(maxSumDataSize, otaPackageService.sumDataSizeByTenantId(tenantId)); } @Test public void testSaveFirmware() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); @@ -93,7 +132,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + firmware.setDataSize(DATA_SIZE); + OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware); Assert.assertNotNull(savedFirmware); Assert.assertNotNull(savedFirmware.getId()); @@ -105,23 +145,52 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Assert.assertEquals(firmware.getData(), savedFirmware.getData()); savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode()); - firmwareService.saveFirmware(savedFirmware); + otaPackageService.saveOtaPackage(savedFirmware); + + OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); + Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle()); + + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); + } + + @Test + public void testSaveFirmwareWithUrl() { + OtaPackageInfo firmware = new OtaPackageInfo(); + firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + firmware.setUrl(URL); + firmware.setDataSize(0L); + OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware); + + Assert.assertNotNull(savedFirmware); + Assert.assertNotNull(savedFirmware.getId()); + Assert.assertTrue(savedFirmware.getCreatedTime() > 0); + Assert.assertEquals(firmware.getTenantId(), savedFirmware.getTenantId()); + Assert.assertEquals(firmware.getTitle(), savedFirmware.getTitle()); + Assert.assertEquals(firmware.getFileName(), savedFirmware.getFileName()); + Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType()); + + savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode()); + otaPackageService.saveOtaPackageInfo(savedFirmware); - Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle()); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } @Test public void testSaveFirmwareInfoAndUpdateWithData() { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setTenantId(tenantId); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo); + OtaPackageInfo savedFirmwareInfo = otaPackageService.saveOtaPackageInfo(firmwareInfo); Assert.assertNotNull(savedFirmwareInfo); Assert.assertNotNull(savedFirmwareInfo.getId()); @@ -129,7 +198,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Assert.assertEquals(firmwareInfo.getTenantId(), savedFirmwareInfo.getTenantId()); Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); - Firmware firmware = new Firmware(savedFirmwareInfo.getId()); + OtaPackage firmware = new OtaPackage(savedFirmwareInfo.getId()); firmware.setCreatedTime(firmwareInfo.getCreatedTime()); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); @@ -141,25 +210,26 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); + firmware.setDataSize(DATA_SIZE); - firmwareService.saveFirmware(firmware); + otaPackageService.saveOtaPackage(firmware); - savedFirmwareInfo = firmwareService.findFirmwareInfoById(tenantId, savedFirmwareInfo.getId()); + savedFirmwareInfo = otaPackageService.findOtaPackageInfoById(tenantId, savedFirmwareInfo.getId()); savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); - firmwareService.saveFirmwareInfo(savedFirmwareInfo); + otaPackageService.saveOtaPackageInfo(savedFirmwareInfo); - Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, firmware.getId()); + OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, firmware.getId()); firmware.setAdditionalInfo(JacksonUtil.newObjectNode()); Assert.assertEquals(foundFirmware.getTitle(), firmware.getTitle()); Assert.assertTrue(foundFirmware.isHasData()); - firmwareService.deleteFirmware(tenantId, savedFirmwareInfo.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmwareInfo.getId()); } @Test public void testSaveFirmwareWithEmptyTenant() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); firmware.setTitle(TITLE); @@ -171,13 +241,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware should be assigned to tenant!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage should be assigned to tenant!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithEmptyType() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setTitle(TITLE); @@ -190,12 +260,12 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { thrown.expect(DataValidationException.class); thrown.expectMessage("Type should be specified!"); - firmwareService.saveFirmware(firmware); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithEmptyTitle() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); @@ -207,13 +277,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware title should be specified!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage title should be specified!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithEmptyFileName() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); @@ -225,13 +295,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware file name should be specified!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage file name should be specified!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithEmptyContentType() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); @@ -243,13 +313,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware content type should be specified!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage content type should be specified!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithEmptyData() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); @@ -261,13 +331,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setChecksum(CHECKSUM); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware data should be specified!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage data should be specified!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithInvalidTenant() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(new TenantId(Uuids.timeBased())); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); @@ -280,13 +350,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware is referencing to non-existent tenant!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage is referencing to non-existent tenant!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithInvalidDeviceProfileId() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(new DeviceProfileId(Uuids.timeBased())); firmware.setType(FIRMWARE); @@ -299,13 +369,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware is referencing to non-existent device profile!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage is referencing to non-existent device profile!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareWithEmptyChecksum() { - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); @@ -317,21 +387,21 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware checksum should be specified!"); - firmwareService.saveFirmware(firmware); + thrown.expectMessage("OtaPackage checksum should be specified!"); + otaPackageService.saveOtaPackage(firmware); } @Test public void testSaveFirmwareInfoWithExistingTitleAndVersion() { - FirmwareInfo firmwareInfo = new FirmwareInfo(); + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); firmwareInfo.setTenantId(tenantId); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); - firmwareService.saveFirmwareInfo(firmwareInfo); + otaPackageService.saveOtaPackageInfo(firmwareInfo); - FirmwareInfo newFirmwareInfo = new FirmwareInfo(); + OtaPackageInfo newFirmwareInfo = new OtaPackageInfo(); newFirmwareInfo.setTenantId(tenantId); newFirmwareInfo.setDeviceProfileId(deviceProfileId); newFirmwareInfo.setType(FIRMWARE); @@ -339,56 +409,21 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { newFirmwareInfo.setVersion(VERSION); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware with such title and version already exists!"); - firmwareService.saveFirmwareInfo(newFirmwareInfo); + thrown.expectMessage("OtaPackage with such title and version already exists!"); + otaPackageService.saveOtaPackageInfo(newFirmwareInfo); } @Test public void testSaveFirmwareWithExistingTitleAndVersion() { - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setDeviceProfileId(deviceProfileId); - firmware.setType(FIRMWARE); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - firmwareService.saveFirmware(firmware); - - Firmware newFirmware = new Firmware(); - newFirmware.setTenantId(tenantId); - newFirmware.setDeviceProfileId(deviceProfileId); - newFirmware.setType(FIRMWARE); - newFirmware.setTitle(TITLE); - newFirmware.setVersion(VERSION); - newFirmware.setFileName(FILE_NAME); - newFirmware.setContentType(CONTENT_TYPE); - newFirmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - newFirmware.setChecksum(CHECKSUM); - newFirmware.setData(DATA); - + createFirmware(tenantId, VERSION); thrown.expect(DataValidationException.class); - thrown.expectMessage("Firmware with such title and version already exists!"); - firmwareService.saveFirmware(newFirmware); + thrown.expectMessage("OtaPackage with such title and version already exists!"); + createFirmware(tenantId, VERSION); } @Test public void testDeleteFirmwareWithReferenceByDevice() { - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setDeviceProfileId(deviceProfileId); - firmware.setType(FIRMWARE); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + OtaPackage savedFirmware = createFirmware(tenantId, VERSION); Device device = new Device(); device.setTenantId(tenantId); @@ -399,44 +434,25 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { try { thrown.expect(DataValidationException.class); - thrown.expectMessage("The firmware referenced by the devices cannot be deleted!"); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + thrown.expectMessage("The otaPackage referenced by the devices cannot be deleted!"); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } finally { deviceService.deleteDevice(tenantId, savedDevice.getId()); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } } @Test - public void testUpdateDeviceProfileIdWithReferenceByDevice() { - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setDeviceProfileId(deviceProfileId); - firmware.setType(FIRMWARE); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); - - Device device = new Device(); - device.setTenantId(tenantId); - device.setName("My device"); - device.setDeviceProfileId(deviceProfileId); - device.setFirmwareId(savedFirmware.getId()); - Device savedDevice = deviceService.saveDevice(device); + public void testUpdateDeviceProfileId() { + OtaPackage savedFirmware = createFirmware(tenantId, VERSION); try { thrown.expect(DataValidationException.class); - thrown.expectMessage("Can`t update deviceProfileId because firmware is already in use!"); + thrown.expectMessage("Updating otaPackage deviceProfile is prohibited!"); savedFirmware.setDeviceProfileId(null); - firmwareService.saveFirmware(savedFirmware); + otaPackageService.saveOtaPackage(savedFirmware); } finally { - deviceService.deleteDevice(tenantId, savedDevice.getId()); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } } @@ -445,38 +461,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Test Device Profile"); DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setDeviceProfileId(savedDeviceProfile.getId()); - firmware.setType(FIRMWARE); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); - - savedDeviceProfile.setFirmwareId(savedFirmware.getId()); - deviceProfileService.saveDeviceProfile(savedDeviceProfile); - - try { - thrown.expect(DataValidationException.class); - thrown.expectMessage("The firmware referenced by the device profile cannot be deleted!"); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); - } finally { - deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); - } - } - - @Test - public void testUpdateDeviceProfileIdWithReferenceByDeviceProfile() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Test Device Profile"); - DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); - - Firmware firmware = new Firmware(); + OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(savedDeviceProfile.getId()); firmware.setType(FIRMWARE); @@ -487,107 +472,87 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + firmware.setDataSize(DATA_SIZE); + OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware); savedDeviceProfile.setFirmwareId(savedFirmware.getId()); deviceProfileService.saveDeviceProfile(savedDeviceProfile); try { thrown.expect(DataValidationException.class); - thrown.expectMessage("Can`t update deviceProfileId because firmware is already in use!"); - savedFirmware.setDeviceProfileId(null); - firmwareService.saveFirmware(savedFirmware); + thrown.expectMessage("The otaPackage referenced by the device profile cannot be deleted!"); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } finally { deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } } @Test public void testFindFirmwareById() { - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setDeviceProfileId(deviceProfileId); - firmware.setType(FIRMWARE); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + OtaPackage savedFirmware = createFirmware(tenantId, VERSION); - Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); Assert.assertNotNull(foundFirmware); Assert.assertEquals(savedFirmware, foundFirmware); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } @Test public void testFindFirmwareInfoById() { - FirmwareInfo firmware = new FirmwareInfo(); + OtaPackageInfo firmware = new OtaPackageInfo(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); - FirmwareInfo savedFirmware = firmwareService.saveFirmwareInfo(firmware); + OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware); - FirmwareInfo foundFirmware = firmwareService.findFirmwareInfoById(tenantId, savedFirmware.getId()); + OtaPackageInfo foundFirmware = otaPackageService.findOtaPackageInfoById(tenantId, savedFirmware.getId()); Assert.assertNotNull(foundFirmware); Assert.assertEquals(savedFirmware, foundFirmware); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); } @Test public void testDeleteFirmware() { - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setDeviceProfileId(deviceProfileId); - firmware.setType(FIRMWARE); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - Firmware savedFirmware = firmwareService.saveFirmware(firmware); + OtaPackage savedFirmware = createFirmware(tenantId, VERSION); - Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); Assert.assertNotNull(foundFirmware); - firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); - foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId()); + foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); Assert.assertNull(foundFirmware); } @Test public void testFindTenantFirmwaresByTenantId() { - List firmwares = new ArrayList<>(); + List firmwares = new ArrayList<>(); for (int i = 0; i < 165; i++) { - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setDeviceProfileId(deviceProfileId); - firmware.setType(FIRMWARE); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION + i); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - - FirmwareInfo info = new FirmwareInfo(firmwareService.saveFirmware(firmware)); + OtaPackageInfo info = new OtaPackageInfo(createFirmware(tenantId, VERSION + i)); info.setHasData(true); firmwares.add(info); } - List loadedFirmwares = new ArrayList<>(); + OtaPackageInfo firmwareWithUrl = new OtaPackageInfo(); + firmwareWithUrl.setTenantId(tenantId); + firmwareWithUrl.setDeviceProfileId(deviceProfileId); + firmwareWithUrl.setType(FIRMWARE); + firmwareWithUrl.setTitle(TITLE); + firmwareWithUrl.setVersion(VERSION); + firmwareWithUrl.setUrl(URL); + firmwareWithUrl.setDataSize(0L); + + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl); + savedFwWithUrl.setHasData(true); + + firmwares.add(savedFwWithUrl); + + List loadedFirmwares = new ArrayList<>(); PageLink pageLink = new PageLink(16); - PageData pageData; + PageData pageData; do { - pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink); + pageData = otaPackageService.findTenantOtaPackagesByTenantId(tenantId, pageLink); loadedFirmwares.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -599,70 +564,50 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Assert.assertEquals(firmwares, loadedFirmwares); - firmwareService.deleteFirmwaresByTenantId(tenantId); + otaPackageService.deleteOtaPackagesByTenantId(tenantId); pageLink = new PageLink(31); - pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink); + pageData = otaPackageService.findTenantOtaPackagesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); } @Test public void testFindTenantFirmwaresByTenantIdAndHasData() { - List firmwares = new ArrayList<>(); + List firmwares = new ArrayList<>(); for (int i = 0; i < 165; i++) { - FirmwareInfo firmwareInfo = new FirmwareInfo(); - firmwareInfo.setTenantId(tenantId); - firmwareInfo.setDeviceProfileId(deviceProfileId); - firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle(TITLE); - firmwareInfo.setVersion(VERSION + i); - firmwareInfo.setFileName(FILE_NAME); - firmwareInfo.setContentType(CONTENT_TYPE); - firmwareInfo.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmwareInfo.setChecksum(CHECKSUM); - firmwareInfo.setDataSize((long) DATA.array().length); - firmwares.add(firmwareService.saveFirmwareInfo(firmwareInfo)); + firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createFirmware(tenantId, VERSION + i)))); } - List loadedFirmwares = new ArrayList<>(); + OtaPackageInfo firmwareWithUrl = new OtaPackageInfo(); + firmwareWithUrl.setTenantId(tenantId); + firmwareWithUrl.setDeviceProfileId(deviceProfileId); + firmwareWithUrl.setType(FIRMWARE); + firmwareWithUrl.setTitle(TITLE); + firmwareWithUrl.setVersion(VERSION); + firmwareWithUrl.setUrl(URL); + firmwareWithUrl.setDataSize(0L); + + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl); + savedFwWithUrl.setHasData(true); + + firmwares.add(savedFwWithUrl); + + List loadedFirmwares = new ArrayList<>(); PageLink pageLink = new PageLink(16); - PageData pageData; + PageData pageData; do { - pageData = firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, false, pageLink); + pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink); loadedFirmwares.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - Collections.sort(firmwares, idComparator); - Collections.sort(loadedFirmwares, idComparator); - - Assert.assertEquals(firmwares, loadedFirmwares); - - firmwares.forEach(f -> { - Firmware firmware = new Firmware(f.getId()); - firmware.setCreatedTime(f.getCreatedTime()); - firmware.setTenantId(f.getTenantId()); - firmware.setDeviceProfileId(deviceProfileId); - firmware.setType(FIRMWARE); - firmware.setTitle(f.getTitle()); - firmware.setVersion(f.getVersion()); - firmware.setFileName(FILE_NAME); - firmware.setContentType(CONTENT_TYPE); - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); - firmware.setChecksum(CHECKSUM); - firmware.setData(DATA); - firmware.setDataSize((long) DATA.array().length); - firmwareService.saveFirmware(firmware); - f.setHasData(true); - }); - loadedFirmwares = new ArrayList<>(); pageLink = new PageLink(16); do { - pageData = firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, true, pageLink); + pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink); loadedFirmwares.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -674,12 +619,28 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Assert.assertEquals(firmwares, loadedFirmwares); - firmwareService.deleteFirmwaresByTenantId(tenantId); + otaPackageService.deleteOtaPackagesByTenantId(tenantId); pageLink = new PageLink(31); - pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink); + pageData = otaPackageService.findTenantOtaPackagesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); } + private OtaPackage createFirmware(TenantId tenantId, String version) { + OtaPackage firmware = new OtaPackage(); + firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); + firmware.setTitle(TITLE); + firmware.setVersion(version); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); + firmware.setData(DATA); + firmware.setDataSize(DATA_SIZE); + return otaPackageService.saveOtaPackage(firmware); + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/OtaPackageServiceSqlTest.java similarity index 83% rename from dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/sql/OtaPackageServiceSqlTest.java index a89414e3a2..59ac4f76bf 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/OtaPackageServiceSqlTest.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.dao.service.sql; -import org.thingsboard.server.dao.service.BaseFirmwareServiceTest; +import org.thingsboard.server.dao.service.BaseOtaPackageServiceTest; import org.thingsboard.server.dao.service.DaoSqlTest; @DaoSqlTest -public class FirmwareServiceSqlTest extends BaseFirmwareServiceTest { +public class OtaPackageServiceSqlTest extends BaseOtaPackageServiceTest { } diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index 2805c78fdd..d7a960471b 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -36,8 +36,11 @@ caffeine.specs.tenantProfiles.maxSize=100000 caffeine.specs.deviceProfiles.timeToLiveInMinutes=1440 caffeine.specs.deviceProfiles.maxSize=100000 -caffeine.specs.firmwares.timeToLiveInMinutes=1440 -caffeine.specs.firmwares.maxSize=100000 +caffeine.specs.otaPackages.timeToLiveInMinutes=1440 +caffeine.specs.otaPackages.maxSize=100000 + +caffeine.specs.otaPackagesData.timeToLiveInMinutes=1440 +caffeine.specs.otaPackagesData.maxSize=100000 caffeine.specs.edges.timeToLiveInMinutes=1440 caffeine.specs.edges.maxSize=100000 diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index f65316f2c1..f7b1b4118d 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -24,12 +24,16 @@ DROP TABLE IF EXISTS dashboard; DROP TABLE IF EXISTS rule_node_state; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; +DROP TABLE IF EXISTS oauth2_mobile; +DROP TABLE IF EXISTS oauth2_domain; +DROP TABLE IF EXISTS oauth2_registration; +DROP TABLE IF EXISTS oauth2_params; +DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; -DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS api_usage_state; DROP TABLE IF EXISTS resource; -DROP TABLE IF EXISTS firmware; +DROP TABLE IF EXISTS ota_package; DROP TABLE IF EXISTS edge; DROP TABLE IF EXISTS edge_event; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index 855a53df2d..a29dea43c2 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -25,9 +25,13 @@ DROP TABLE IF EXISTS rule_node_state; DROP TABLE IF EXISTS rule_node; DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS tb_schema_settings; +DROP TABLE IF EXISTS oauth2_mobile; +DROP TABLE IF EXISTS oauth2_domain; +DROP TABLE IF EXISTS oauth2_registration; +DROP TABLE IF EXISTS oauth2_params; +DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; -DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS api_usage_state; DROP TABLE IF EXISTS resource; DROP TABLE IF EXISTS firmware; diff --git a/pom.xml b/pom.xml index e09dedf49f..b7d0eb71f6 100755 --- a/pom.xml +++ b/pom.xml @@ -69,9 +69,7 @@ 2.12.1 2.2.6 2.6.1 - 1.3.1 - 1.3.1 - 1.3.1 + 2.0.0-M3 2.6.2 2.3.30 1.6.2 @@ -1222,22 +1220,22 @@ org.eclipse.leshan leshan-server-cf - ${leshan-server.version} + ${leshan.version} org.eclipse.leshan leshan-client-cf - ${leshan-client.version} + ${leshan.version} org.eclipse.leshan leshan-server-redis - ${leshan-server.version} + ${leshan.version} org.eclipse.leshan leshan-core - ${leshan-core.version} + ${leshan.version} org.eclipse.californium diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 433e2f8d96..14a008634b 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -19,18 +19,25 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpRequest; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; import org.springframework.http.client.support.HttpRequestWrapper; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.util.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import org.springframework.web.multipart.MultipartFile; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.rest.client.utils.RestJsonConverter; import org.thingsboard.server.common.data.AdminSettings; @@ -48,6 +55,10 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; +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.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; @@ -78,8 +89,10 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.id.UserId; @@ -90,7 +103,9 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; -import org.thingsboard.server.common.data.oauth2.OAuth2ClientsParams; +import org.thingsboard.server.common.data.oauth2.OAuth2Info; +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.page.SortOrder; @@ -129,7 +144,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Collectors; @@ -147,7 +161,6 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { private final ObjectMapper objectMapper = new ObjectMapper(); private ExecutorService service = ThingsBoardExecutors.newWorkStealingPool(10, getClass()); - protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; public RestClient(String baseURL) { @@ -1238,6 +1251,21 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { HttpEntity.EMPTY, Device.class, tenantId, deviceId).getBody(); } + public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(OtaPackageType otaPackageType, DeviceProfileId deviceProfileId) { + Map params = new HashMap<>(); + params.put("otaPackageType", otaPackageType.name()); + params.put("deviceProfileId", deviceProfileId.getId().toString()); + + return restTemplate.exchange( + baseURL + "/api/devices/count/{otaPackageType}?deviceProfileId={deviceProfileId}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference() { + }, + params + ).getBody(); + } + @Deprecated public Device createDevice(String name, String type) { Device device = new Device(); @@ -1744,21 +1772,28 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } - public List getOAuth2Clients() { + public List getOAuth2Clients(String pkgName) { + Map params = new HashMap<>(); + StringBuilder urlBuilder = new StringBuilder(baseURL); + urlBuilder.append("/api/noauth/oauth2Clients"); + if (pkgName != null) { + urlBuilder.append("?pkgName={pkgName}"); + params.put("pkgName", pkgName); + } return restTemplate.exchange( - baseURL + "/api/noauth/oauth2Clients", + urlBuilder.toString(), HttpMethod.POST, HttpEntity.EMPTY, new ParameterizedTypeReference>() { - }).getBody(); + }, params).getBody(); } - public OAuth2ClientsParams getCurrentOAuth2Params() { - return restTemplate.getForEntity(baseURL + "/api/oauth2/config", OAuth2ClientsParams.class).getBody(); + public OAuth2Info getCurrentOAuth2Info() { + return restTemplate.getForEntity(baseURL + "/api/oauth2/config", OAuth2Info.class).getBody(); } - public OAuth2ClientsParams saveOAuth2Params(OAuth2ClientsParams oauth2Params) { - return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Params, OAuth2ClientsParams.class).getBody(); + public OAuth2Info saveOAuth2Info(OAuth2Info oauth2Info) { + return restTemplate.postForEntity(baseURL + "/api/oauth2/config", oauth2Info, OAuth2Info.class).getBody(); } public String getLoginProcessingUrl() { @@ -2830,6 +2865,176 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForEntity(baseURL + "/api/edge/sync/{edgeId}", null, EdgeId.class, params); } + public ResponseEntity downloadResource(TbResourceId resourceId) { + Map params = new HashMap<>(); + params.put("resourceId", resourceId.getId().toString()); + + return restTemplate.exchange( + baseURL + "/api/resource/{resourceId}/download", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference<>() {}, + params + ); + } + + public TbResourceInfo getResourceInfoById(TbResourceId resourceId) { + Map params = new HashMap<>(); + params.put("resourceId", resourceId.getId().toString()); + + return restTemplate.exchange( + baseURL + "/api/resource/info/{resourceId}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference() {}, + params + ).getBody(); + } + + public TbResource getResourceId(TbResourceId resourceId) { + Map params = new HashMap<>(); + params.put("resourceId", resourceId.getId().toString()); + + return restTemplate.exchange( + baseURL + "/api/resource/{resourceId}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference() {}, + params + ).getBody(); + } + + public TbResource saveResource(TbResource resource) { + return restTemplate.postForEntity( + baseURL + "/api/resource", + resource, + TbResource.class + ).getBody(); + } + + public PageData getResources(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/resource?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() {}, + params + ).getBody(); + } + + public void deleteResource(TbResourceId resourceId) { + restTemplate.delete("/api/resource/{resourceId}", resourceId.getId().toString()); + } + + public ResponseEntity downloadOtaPackage(OtaPackageId otaPackageId) { + Map params = new HashMap<>(); + params.put("otaPackageId", otaPackageId.getId().toString()); + + return restTemplate.exchange( + baseURL + "/api/otaPackage/{otaPackageId}/download", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference<>() {}, + params + ); + } + + public OtaPackageInfo getOtaPackageInfoById(OtaPackageId otaPackageId) { + Map params = new HashMap<>(); + params.put("otaPackageId", otaPackageId.getId().toString()); + + return restTemplate.exchange( + baseURL + "/api/otaPackage/info/{otaPackageId}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference() {}, + params + ).getBody(); + } + + public OtaPackage getOtaPackageById(OtaPackageId otaPackageId) { + Map params = new HashMap<>(); + params.put("otaPackageId", otaPackageId.getId().toString()); + + return restTemplate.exchange( + baseURL + "/api/otaPackage/{otaPackageId}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference() {}, + params + ).getBody(); + } + + public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) { + return restTemplate.postForEntity(baseURL + "/api/otaPackage", otaPackageInfo, OtaPackageInfo.class).getBody(); + } + + public OtaPackage saveOtaPackageData(OtaPackageId otaPackageId, String checkSum, ChecksumAlgorithm checksumAlgorithm, MultipartFile file) throws Exception { + HttpHeaders header = new HttpHeaders(); + header.setContentType(MediaType.MULTIPART_FORM_DATA); + + MultiValueMap fileMap = new LinkedMultiValueMap<>(); + fileMap.add(HttpHeaders.CONTENT_DISPOSITION, "form-data; name=file; filename=" + file.getName()); + HttpEntity fileEntity = new HttpEntity<>(new ByteArrayResource(file.getBytes()), fileMap); + + MultiValueMap body = new LinkedMultiValueMap<>(); + body.add("file", fileEntity); + HttpEntity> requestEntity = new HttpEntity<>(body, header); + + Map params = new HashMap<>(); + params.put("otaPackageId", otaPackageId.getId().toString()); + params.put("checksumAlgorithm", checksumAlgorithm.name()); + String url = "/api/otaPackage/{otaPackageId}?checksumAlgorithm={checksumAlgorithm}"; + + if(checkSum != null) { + url += "&checkSum={checkSum}"; + } + + return restTemplate.postForEntity( + baseURL + url, requestEntity, OtaPackage.class, params + ).getBody(); + } + + public PageData getOtaPackages(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/otaPackages?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params + ).getBody(); + } + + public PageData getOtaPackages(DeviceProfileId deviceProfileId, + OtaPackageType otaPackageType, + boolean hasData, + PageLink pageLink) { + Map params = new HashMap<>(); + params.put("hasData", String.valueOf(hasData)); + params.put("deviceProfileId", deviceProfileId.getId().toString()); + params.put("type", otaPackageType.name()); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/otaPackages/{deviceProfileId}/{type}/{hasData}?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params + ).getBody(); + } + + public void deleteOtaPackage(OtaPackageId otaPackageId) { + restTemplate.delete(baseURL + "/api/otaPackage/{otaPackageId}", otaPackageId.getId().toString()); + } + @Deprecated public Optional getAttributes(String accessToken, String clientKeys, String sharedKeys) { Map params = new HashMap<>(); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 7a0e3cce63..43e3a5e329 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -47,7 +47,9 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.TbResultSetFuture; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -202,6 +204,10 @@ public interface TbContext { EntityViewService getEntityViewService(); + ResourceService getResourceService(); + + OtaPackageService getOtaPackageService(); + RuleEngineDeviceProfileCache getDeviceProfileCache(); EdgeService getEdgeService(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java index e84beae702..6b7a695eea 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.exception.ApiUsageLimitsExceededException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; @@ -150,6 +151,8 @@ class DeviceState { stateChanged = processAlarmClearNotification(ctx, msg); } else if (msg.getType().equals(DataConstants.ALARM_ACK)) { processAlarmAckNotification(ctx, msg); + } else if (msg.getType().equals(DataConstants.ALARM_DELETE)) { + processAlarmDeleteNotification(ctx, msg); } else { if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED) || msg.getType().equals(DataConstants.ENTITY_UNASSIGNED)) { dynamicPredicateValueCtx.resetCustomer(); @@ -193,6 +196,12 @@ class DeviceState { ctx.tellSuccess(msg); } + private void processAlarmDeleteNotification(TbContext ctx, TbMsg msg) { + Alarm alarm = JacksonUtil.fromString(msg.getData(), Alarm.class); + alarmStates.values().removeIf(alarmState -> alarmState.getCurrentAlarm().getId().equals(alarm.getId())); + ctx.tellSuccess(msg); + } + private boolean processAttributesUpdateNotification(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { String scope = msg.getMetaData().getValue(DataConstants.SCOPE); if (StringUtils.isEmpty(scope)) { @@ -253,7 +262,12 @@ class DeviceState { for (DeviceProfileAlarm alarm : deviceProfile.getAlarmSettings()) { AlarmState alarmState = alarmStates.computeIfAbsent(alarm.getId(), a -> new AlarmState(this.deviceProfile, deviceId, alarm, getOrInitPersistedAlarmState(alarm), dynamicPredicateValueCtx)); - stateChanged |= alarmState.process(ctx, msg, latestValues, update); + try { + stateChanged |= alarmState.process(ctx, msg, latestValues, update); + } catch (ApiUsageLimitsExceededException e) { + alarmStates.remove(alarm.getId()); + throw e; + } } } } diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 39acda4590..d81b28a5ff 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -114,13 +114,14 @@ transport: private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" + skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: enable: "${LWM2M_ENABLED_BS:true}" id: "${LWM2M_SERVER_ID_BS:111}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" bind_port: "${LWM2M_BIND_PORT_BS:5687}" security: - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" + bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_BS:0.0.0.0}" bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" # Only for RPK: Public & Private Key. If the keystore file is missing or not working public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" diff --git a/ui-ngx/src/app/core/api/data-aggregator.ts b/ui-ngx/src/app/core/api/data-aggregator.ts index 02c1c8649a..971f037282 100644 --- a/ui-ngx/src/app/core/api/data-aggregator.ts +++ b/ui-ngx/src/app/core/api/data-aggregator.ts @@ -16,16 +16,17 @@ import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models'; import { - AggregationType, calculateIntervalComparisonEndTime, - calculateIntervalEndTime, calculateIntervalStartEndTime, + AggregationType, + calculateIntervalComparisonEndTime, + calculateIntervalEndTime, + calculateIntervalStartEndTime, getCurrentTime, - getCurrentTimeForComparison, getTime, + getTime, SubscriptionTimewindow } from '@shared/models/time/time.models'; import { UtilsService } from '@core/services/utils.service'; -import { deepClone } from '@core/utils'; +import { deepClone, isNumeric } from '@core/utils'; import Timeout = NodeJS.Timeout; -import * as moment_ from 'moment'; export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void; @@ -407,24 +408,11 @@ export class DataAggregator { } } - private isNumeric(val: any): boolean { - return (val - parseFloat( val ) + 1) >= 0; - } - private convertValue(val: string): any { - if (!this.noAggregation || val && this.isNumeric(val)) { + if (!this.noAggregation || val && isNumeric(val) && Number(val).toString() === val) { return Number(val); - } else { - return val; - } - } - - private getCurrentTime() { - if (this.subsTw.timeForComparison) { - return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone); - } else { - return getCurrentTime(this.subsTw.timezone); } + return val; } } 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 9c769f3777..ff828cc2ac 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -38,7 +38,7 @@ import { } from '@shared/models/telemetry/telemetry.models'; import { UtilsService } from '@core/services/utils.service'; import { EntityDataListener, EntityDataLoadResult } from '@core/api/entity-data.service'; -import { deepClone, isDefined, isDefinedAndNotNull, isObject, objectHashCode } from '@core/utils'; +import { deepClone, isDefined, isDefinedAndNotNull, isNumeric, isObject, objectHashCode } from '@core/utils'; import { PageData } from '@shared/models/page/page-data'; import { DataAggregator } from '@core/api/data-aggregator'; import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -667,8 +667,7 @@ export class EntityDataSubscription { if (prevDataCb) { dataAggregator.updateOnDataCb(prevDataCb); } - } - if (!this.history && !isUpdate) { + } else if (!this.history && !isUpdate) { this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, dataUpdatedCb); } } @@ -743,16 +742,11 @@ export class EntityDataSubscription { } } - private isNumeric(val: any): boolean { - return (val - parseFloat( val ) + 1) >= 0; - } - private convertValue(val: string): any { - if (val && this.isNumeric(val) && Number(val).toString() === val) { + if (val && isNumeric(val) && Number(val).toString() === val) { return Number(val); - } else { - return val; } + return val; } private toSubscriptionData(sourceData: {[key: string]: TsValue | TsValue[]}, isTs: boolean): SubscriptionData { 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 8c6ac47ba5..515e5397f1 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -152,6 +152,7 @@ export interface IStateController { getStateIndex(): number; getStateIdAtIndex(index: number): string; getEntityId(entityParamName: string): EntityId; + getCurrentStateName(): string; } export interface SubscriptionInfo { diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 4d25e5ce72..68724fa0dc 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -45,6 +45,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions' import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { AlertDialogComponent } from '@shared/components/dialog/alert-dialog.component'; import { OAuth2ClientInfo } from '@shared/models/oauth2.models'; +import { isDefinedAndNotNull, isMobileApp } from '@core/utils'; @Injectable({ providedIn: 'root' @@ -194,15 +195,21 @@ export class AuthService { } public gotoDefaultPlace(isAuthenticated: boolean) { - const authState = getCurrentAuthState(this.store); - const url = this.defaultUrl(isAuthenticated, authState); - this.zone.run(() => { - this.router.navigateByUrl(url); - }); + if (!isMobileApp()) { + const authState = getCurrentAuthState(this.store); + const url = this.defaultUrl(isAuthenticated, authState); + this.zone.run(() => { + this.router.navigateByUrl(url); + }); + } } - public loadOAuth2Clients(): Observable> { - return this.http.post>(`/api/noauth/oauth2Clients`, + public loadOAuth2Clients(pkgName?: string): Observable> { + let url = '/api/noauth/oauth2Clients'; + if (isDefinedAndNotNull(pkgName)) { + url += `?pkgName=${pkgName}`; + } + return this.http.post>(url, null, defaultHttpOptions()).pipe( catchError(err => of([])), tap((OAuth2Clients) => { @@ -516,12 +523,15 @@ export class AuthService { return this.refreshTokenSubject !== null; } - public setUserFromJwtToken(jwtToken, refreshToken, notify) { + public setUserFromJwtToken(jwtToken, refreshToken, notify): Observable { + const authenticatedSubject = new ReplaySubject(); if (!jwtToken) { AuthService.clearTokenData(); if (notify) { this.notifyUnauthenticated(); } + authenticatedSubject.next(false); + authenticatedSubject.complete(); } else { this.updateAndValidateTokens(jwtToken, refreshToken, true); if (notify) { @@ -530,16 +540,30 @@ export class AuthService { (authPayload) => { this.notifyUserLoaded(true); this.notifyAuthenticated(authPayload); + authenticatedSubject.next(true); + authenticatedSubject.complete(); }, () => { this.notifyUserLoaded(true); this.notifyUnauthenticated(); + authenticatedSubject.next(false); + authenticatedSubject.complete(); } ); } else { - this.loadUser(false).subscribe(); + this.loadUser(false).subscribe( + () => { + authenticatedSubject.next(true); + authenticatedSubject.complete(); + }, + () => { + authenticatedSubject.next(false); + authenticatedSubject.complete(); + } + ); } } + return authenticatedSubject; } private updateAndValidateTokens(jwtToken, refreshToken, notify: boolean) { diff --git a/ui-ngx/src/app/core/guards/auth.guard.ts b/ui-ngx/src/app/core/guards/auth.guard.ts index fdba07b94a..98fd4bd969 100644 --- a/ui-ngx/src/app/core/guards/auth.guard.ts +++ b/ui-ngx/src/app/core/guards/auth.guard.ts @@ -29,6 +29,7 @@ import { DialogService } from '@core/services/dialog.service'; import { TranslateService } from '@ngx-translate/core'; import { UtilsService } from '@core/services/utils.service'; import { isObject } from '@core/utils'; +import { MobileService } from '@core/services/mobile.service'; @Injectable({ providedIn: 'root' @@ -41,6 +42,7 @@ export class AuthGuard implements CanActivate, CanActivateChild { private dialogService: DialogService, private utils: UtilsService, private translate: TranslateService, + private mobileService: MobileService, private zone: NgZone) {} getAuthState(): Observable { @@ -108,6 +110,10 @@ export class AuthGuard implements CanActivate, CanActivateChild { return of(false); } } + if (this.mobileService.isMobileApp() && !path.startsWith('dashboard.')) { + this.mobileService.handleMobileNavigation(path, params); + return of(false); + } const defaultUrl = this.authService.defaultUrl(true, authState, path, params); if (defaultUrl) { // this.authService.gotoDefaultPlace(true); diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index 7e3183255a..556567e47b 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -14,16 +14,18 @@ /// limitations under the License. /// -import {Injectable} from '@angular/core'; -import {HttpClient} from '@angular/common/http'; -import {PageLink} from '@shared/models/page/page-link'; -import {defaultHttpOptionsFromConfig, RequestConfig} from './http-utils'; -import {Observable} from 'rxjs'; -import {PageData} from '@shared/models/page/page-data'; -import {DeviceProfile, DeviceProfileInfo, DeviceTransportType} from '@shared/models/device.models'; -import {isDefinedAndNotNull, isEmptyStr} from '@core/utils'; -import {ObjectLwM2M, ServerSecurityConfig} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; -import {SortOrder} from '@shared/models/page/sort-order'; +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable, throwError } from 'rxjs'; +import { PageData } from '@shared/models/page/page-data'; +import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; +import { isDefinedAndNotNull, isEmptyStr } from '@core/utils'; +import { ObjectLwM2M, ServerSecurityConfig } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; +import { SortOrder } from '@shared/models/page/sort-order'; +import { OtaPackageService } from '@core/http/ota-package.service'; +import { mergeMap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' @@ -31,7 +33,8 @@ import {SortOrder} from '@shared/models/page/sort-order'; export class DeviceProfileService { constructor( - private http: HttpClient + private http: HttpClient, + private otaPackageService: OtaPackageService ) { } @@ -70,6 +73,13 @@ export class DeviceProfileService { ); } + public saveDeviceProfileAndConfirmOtaChange(originDeviceProfile: DeviceProfile, deviceProfile: DeviceProfile, + config?: RequestConfig): Observable { + return this.otaPackageService.confirmDialogUpdatePackage(deviceProfile, originDeviceProfile).pipe( + mergeMap((update) => update ? this.saveDeviceProfile(deviceProfile, config) : throwError('Canceled saving device profiles')) + ); + } + public saveDeviceProfile(deviceProfile: DeviceProfile, config?: RequestConfig): Observable { return this.http.post('/api/deviceProfile', deviceProfile, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index c5ef4b8db4..c58e5ac909 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -75,12 +75,12 @@ import { StringOperation } from '@shared/models/query/query.models'; import { alarmFields } from '@shared/models/alarm.models'; -import { FirmwareService } from '@core/http/firmware.service'; -import { EdgeService } from "@core/http/edge.service"; +import { OtaPackageService } from '@core/http/ota-package.service'; +import { EdgeService } from '@core/http/edge.service'; import { Edge, EdgeEventType } from '@shared/models/edge.models'; -import { RuleChainType } from "@shared/models/rule-chain.models"; -import { WidgetService } from "@core/http/widget.service"; -import { DeviceProfileService } from "@core/http/device-profile.service"; +import { RuleChainType } from '@shared/models/rule-chain.models'; +import { WidgetService } from '@core/http/widget.service'; +import { DeviceProfileService } from '@core/http/device-profile.service'; @Injectable({ providedIn: 'root' @@ -101,7 +101,7 @@ export class EntityService { private dashboardService: DashboardService, private entityRelationService: EntityRelationService, private attributeService: AttributeService, - private firmwareService: FirmwareService, + private otaPackageService: OtaPackageService, private widgetService: WidgetService, private deviceProfileService: DeviceProfileService, private utils: UtilsService @@ -142,8 +142,8 @@ export class EntityService { case EntityType.ALARM: console.error('Get Alarm Entity is not implemented!'); break; - case EntityType.FIRMWARE: - observable = this.firmwareService.getFirmwareInfo(entityId, config); + case EntityType.OTA_PACKAGE: + observable = this.otaPackageService.getOtaPackageInfo(entityId, config); break; } return observable; @@ -359,9 +359,9 @@ export class EntityService { case EntityType.ALARM: console.error('Get Alarm Entities is not implemented!'); break; - case EntityType.FIRMWARE: + case EntityType.OTA_PACKAGE: pageLink.sortOrder.property = 'title'; - entitiesObservable = this.firmwareService.getFirmwares(pageLink, config); + entitiesObservable = this.otaPackageService.getOtaPackages(pageLink, config); break; } return entitiesObservable; diff --git a/ui-ngx/src/app/core/http/firmware.service.ts b/ui-ngx/src/app/core/http/firmware.service.ts deleted file mode 100644 index 1fe51c5f68..0000000000 --- a/ui-ngx/src/app/core/http/firmware.service.ts +++ /dev/null @@ -1,123 +0,0 @@ -/// -/// Copyright © 2016-2021 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { PageLink } from '@shared/models/page/page-link'; -import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils'; -import { Observable } from 'rxjs'; -import { PageData } from '@shared/models/page/page-data'; -import { ChecksumAlgorithm, Firmware, FirmwareInfo, FirmwareType } from '@shared/models/firmware.models'; -import { catchError, map, mergeMap } from 'rxjs/operators'; -import { deepClone } from '@core/utils'; - -@Injectable({ - providedIn: 'root' -}) -export class FirmwareService { - constructor( - private http: HttpClient - ) { - - } - - public getFirmwares(pageLink: PageLink, config?: RequestConfig): Observable> { - return this.http.get>(`/api/firmwares${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); - } - - public getFirmwaresInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: FirmwareType, - hasData = true, config?: RequestConfig): Observable> { - const url = `/api/firmwares/${deviceProfileId}/${type}/${hasData}${pageLink.toQuery()}`; - return this.http.get>(url, defaultHttpOptionsFromConfig(config)); - } - - public getFirmware(firmwareId: string, config?: RequestConfig): Observable { - return this.http.get(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config)); - } - - public getFirmwareInfo(firmwareId: string, config?: RequestConfig): Observable { - return this.http.get(`/api/firmware/info/${firmwareId}`, defaultHttpOptionsFromConfig(config)); - } - - public downloadFirmware(firmwareId: string): Observable { - return this.http.get(`/api/firmware/${firmwareId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( - map((response) => { - const headers = response.headers; - const filename = headers.get('x-filename'); - const contentType = headers.get('content-type'); - const linkElement = document.createElement('a'); - try { - const blob = new Blob([response.body], { type: contentType }); - const url = URL.createObjectURL(blob); - linkElement.setAttribute('href', url); - linkElement.setAttribute('download', filename); - const clickEvent = new MouseEvent('click', - { - view: window, - bubbles: true, - cancelable: false - } - ); - linkElement.dispatchEvent(clickEvent); - return null; - } catch (e) { - throw e; - } - }) - ); - } - - public saveFirmware(firmware: Firmware, config?: RequestConfig): Observable { - if (!firmware.file) { - return this.saveFirmwareInfo(firmware, config); - } - const firmwareInfo = deepClone(firmware); - delete firmwareInfo.file; - delete firmwareInfo.checksum; - delete firmwareInfo.checksumAlgorithm; - return this.saveFirmwareInfo(firmwareInfo, config).pipe( - mergeMap(res => { - return this.uploadFirmwareFile(res.id.id, firmware.file, firmware.checksumAlgorithm, firmware.checksum).pipe( - catchError(() => this.deleteFirmware(res.id.id)) - ); - }) - ); - } - - public saveFirmwareInfo(firmware: FirmwareInfo, config?: RequestConfig): Observable { - return this.http.post('/api/firmware', firmware, defaultHttpOptionsFromConfig(config)); - } - - public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm: ChecksumAlgorithm, - checksum?: string, config?: RequestConfig): Observable { - if (!config) { - config = {}; - } - const formData = new FormData(); - formData.append('file', file); - let url = `/api/firmware/${firmwareId}?checksumAlgorithm=${checksumAlgorithm}`; - if (checksum) { - url += `&checksum=${checksum}`; - } - return this.http.post(url, formData, - defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); - } - - public deleteFirmware(firmwareId: string, config?: RequestConfig) { - return this.http.delete(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config)); - } - -} diff --git a/ui-ngx/src/app/core/http/oauth2.service.ts b/ui-ngx/src/app/core/http/oauth2.service.ts index e81d472130..72a6bc77af 100644 --- a/ui-ngx/src/app/core/http/oauth2.service.ts +++ b/ui-ngx/src/app/core/http/oauth2.service.ts @@ -18,7 +18,7 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; -import { OAuth2ClientRegistrationTemplate, OAuth2ClientsParams } from '@shared/models/oauth2.models'; +import { OAuth2ClientRegistrationTemplate, OAuth2Info } from '@shared/models/oauth2.models'; @Injectable({ providedIn: 'root' @@ -29,16 +29,16 @@ export class OAuth2Service { private http: HttpClient ) { } - public getOAuth2Settings(config?: RequestConfig): Observable { - return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); + public getOAuth2Settings(config?: RequestConfig): Observable { + return this.http.get(`/api/oauth2/config`, defaultHttpOptionsFromConfig(config)); } public getOAuth2Template(config?: RequestConfig): Observable> { return this.http.get>(`/api/oauth2/config/template`, defaultHttpOptionsFromConfig(config)); } - public saveOAuth2Settings(OAuth2Setting: OAuth2ClientsParams, config?: RequestConfig): Observable { - return this.http.post('/api/oauth2/config', OAuth2Setting, + public saveOAuth2Settings(OAuth2Setting: OAuth2Info, config?: RequestConfig): Observable { + return this.http.post('/api/oauth2/config', OAuth2Setting, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/http/ota-package.service.ts b/ui-ngx/src/app/core/http/ota-package.service.ts new file mode 100644 index 0000000000..1f9a36882a --- /dev/null +++ b/ui-ngx/src/app/core/http/ota-package.service.ts @@ -0,0 +1,167 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils'; +import { forkJoin, Observable, of } from 'rxjs'; +import { PageData } from '@shared/models/page/page-data'; +import { + ChecksumAlgorithm, + OtaPackage, + OtaPackageInfo, + OtaPagesIds, + OtaUpdateType +} from '@shared/models/ota-package.models'; +import { catchError, map, mergeMap } from 'rxjs/operators'; +import { deepClone } from '@core/utils'; +import { BaseData } from '@shared/models/base-data'; +import { EntityId } from '@shared/models/id/entity-id'; +import { TranslateService } from '@ngx-translate/core'; +import { DialogService } from '@core/services/dialog.service'; + +@Injectable({ + providedIn: 'root' +}) +export class OtaPackageService { + constructor( + private http: HttpClient, + private translate: TranslateService, + private dialogService: DialogService + ) { + + } + + public getOtaPackages(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/otaPackages${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + + public getOtaPackagesInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: OtaUpdateType, + config?: RequestConfig): Observable> { + const url = `/api/otaPackages/${deviceProfileId}/${type}${pageLink.toQuery()}`; + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); + } + + public getOtaPackage(otaPackageId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/otaPackages/${otaPackageId}`, defaultHttpOptionsFromConfig(config)); + } + + public getOtaPackageInfo(otaPackageId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/otaPackage/info/${otaPackageId}`, defaultHttpOptionsFromConfig(config)); + } + + public downloadOtaPackage(otaPackageId: string): Observable { + return this.http.get(`/api/otaPackage/${otaPackageId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( + map((response) => { + const headers = response.headers; + const filename = headers.get('x-filename'); + const contentType = headers.get('content-type'); + const linkElement = document.createElement('a'); + try { + const blob = new Blob([response.body], { type: contentType }); + const url = URL.createObjectURL(blob); + linkElement.setAttribute('href', url); + linkElement.setAttribute('download', filename); + const clickEvent = new MouseEvent('click', + { + view: window, + bubbles: true, + cancelable: false + } + ); + linkElement.dispatchEvent(clickEvent); + return null; + } catch (e) { + throw e; + } + }) + ); + } + + public saveOtaPackage(otaPackage: OtaPackage, config?: RequestConfig): Observable { + if (!otaPackage.file) { + return this.saveOtaPackageInfo(otaPackage, config); + } + const otaPackageInfo = deepClone(otaPackage); + delete otaPackageInfo.file; + delete otaPackageInfo.checksum; + delete otaPackageInfo.checksumAlgorithm; + return this.saveOtaPackageInfo(otaPackageInfo, config).pipe( + mergeMap(res => { + return this.uploadOtaPackageFile(res.id.id, otaPackage.file, otaPackage.checksumAlgorithm, otaPackage.checksum).pipe( + catchError(() => this.deleteOtaPackage(res.id.id)) + ); + }) + ); + } + + public saveOtaPackageInfo(otaPackageInfo: OtaPackageInfo, config?: RequestConfig): Observable { + return this.http.post('/api/otaPackage', otaPackageInfo, defaultHttpOptionsFromConfig(config)); + } + + public uploadOtaPackageFile(otaPackageId: string, file: File, checksumAlgorithm: ChecksumAlgorithm, + checksum?: string, config?: RequestConfig): Observable { + if (!config) { + config = {}; + } + const formData = new FormData(); + formData.append('file', file); + let url = `/api/otaPackage/${otaPackageId}?checksumAlgorithm=${checksumAlgorithm}`; + if (checksum) { + url += `&checksum=${checksum}`; + } + return this.http.post(url, formData, + defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); + } + + public deleteOtaPackage(otaPackageId: string, config?: RequestConfig) { + return this.http.delete(`/api/otaPackage/${otaPackageId}`, defaultHttpOptionsFromConfig(config)); + } + + public countUpdateDeviceAfterChangePackage(type: OtaUpdateType, entityId: EntityId, config?: RequestConfig): Observable { + return this.http.get(`/api/devices/count/${type}?deviceProfileId=${entityId.id}`, defaultHttpOptionsFromConfig(config)); + } + + public confirmDialogUpdatePackage(entity: BaseData&OtaPagesIds, + originEntity: BaseData&OtaPagesIds): Observable { + const tasks: Observable[] = []; + if (originEntity?.id?.id && originEntity.firmwareId?.id !== entity.firmwareId?.id) { + tasks.push(this.countUpdateDeviceAfterChangePackage(OtaUpdateType.FIRMWARE, entity.id)); + } else { + tasks.push(of(0)); + } + if (originEntity?.id?.id && originEntity.softwareId?.id !== entity.softwareId?.id) { + tasks.push(this.countUpdateDeviceAfterChangePackage(OtaUpdateType.SOFTWARE, entity.id)); + } else { + tasks.push(of(0)); + } + return forkJoin(tasks).pipe( + mergeMap(([deviceFirmwareUpdate, deviceSoftwareUpdate]) => { + let text = ''; + if (deviceFirmwareUpdate > 0) { + text += this.translate.instant('ota-update.change-firmware', {count: deviceFirmwareUpdate}); + } + if (deviceSoftwareUpdate > 0) { + text += text.length ? ' ' : ''; + text += this.translate.instant('ota-update.change-software', {count: deviceSoftwareUpdate}); + } + return text !== '' ? this.dialogService.confirm('', text, null, this.translate.instant('common.proceed')) : of(true); + }) + ); + } + +} diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index f667fb5788..0dc143e6cb 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -231,7 +231,6 @@ export class DashboardUtilsService { private createDefaultGridSettings(): GridSettings { return { backgroundColor: '#eeeeee', - color: 'rgba(0,0,0,0.870588)', columns: 24, margin: 10, backgroundSizeMode: '100%' diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 07a6d8f237..806447f112 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -276,9 +276,9 @@ export class MenuService { }, { id: guid(), - name: 'firmware.firmware', + name: 'ota-update.ota-updates', type: 'link', - path: '/firmwares', + path: '/otaUpdates', icon: 'memory' }, { @@ -423,9 +423,9 @@ export class MenuService { path: '/deviceProfiles' }, { - name: 'firmware.firmware', + name: 'ota-update.ota-updates', icon: 'memory', - path: '/firmwares' + path: '/otaUpdates' } ] }, diff --git a/ui-ngx/src/app/core/services/mobile.service.ts b/ui-ngx/src/app/core/services/mobile.service.ts index b6774f37d0..cc940d6f85 100644 --- a/ui-ngx/src/app/core/services/mobile.service.ts +++ b/ui-ngx/src/app/core/services/mobile.service.ts @@ -20,9 +20,15 @@ import { isDefined } from '@core/utils'; import { MobileActionResult, WidgetMobileActionResult, WidgetMobileActionType } from '@shared/models/widget.models'; import { from, of } from 'rxjs'; import { Observable } from 'rxjs/internal/Observable'; -import { catchError } from 'rxjs/operators'; +import { catchError, tap } from 'rxjs/operators'; +import { OpenDashboardMessage, ReloadUserMessage, WindowMessage } from '@shared/models/window-message.model'; +import { Params, Router } from '@angular/router'; +import { AuthService } from '@core/auth/auth.service'; const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler'; +const dashboardLoadedHandler = 'tbMobileDashboardLoadedHandler'; +const dashboardLayoutHandler = 'tbMobileDashboardLayoutHandler'; +const navigationHandler = 'tbMobileNavigationHandler'; const mobileHandler = 'tbMobileHandler'; // @dynamic @@ -34,10 +40,21 @@ export class MobileService { private readonly mobileApp; private readonly mobileChannel; - constructor(@Inject(WINDOW) private window: Window) { + private readonly onWindowMessageListener = this.onWindowMessage.bind(this); + + private reloadUserObservable: Observable; + private lastDashboardId: string; + private toggleLayoutFunction: () => void; + + constructor(@Inject(WINDOW) private window: Window, + private router: Router, + private authService: AuthService) { const w = (this.window as any); this.mobileChannel = w.flutter_inappwebview; this.mobileApp = isDefined(this.mobileChannel); + if (this.mobileApp) { + window.addEventListener('message', this.onWindowMessageListener); + } } public isMobileApp(): boolean { @@ -50,6 +67,26 @@ export class MobileService { } } + public onDashboardLoaded(hasRightLayout: boolean, rightLayoutOpened: boolean) { + if (this.mobileApp) { + this.mobileChannel.callHandler(dashboardLoadedHandler, hasRightLayout, rightLayoutOpened); + } + } + + public onDashboardRightLayoutChanged(opened: boolean) { + if (this.mobileApp) { + this.mobileChannel.callHandler(dashboardLayoutHandler, opened); + } + } + + public registerToggleLayoutFunction(toggleLayoutFunction: () => void) { + this.toggleLayoutFunction = toggleLayoutFunction; + } + + public unregisterToggleLayoutFunction() { + this.toggleLayoutFunction = null; + } + public handleWidgetMobileAction(type: WidgetMobileActionType, ...args: any[]): Observable> { if (this.mobileApp) { @@ -67,4 +104,87 @@ export class MobileService { } } + public handleMobileNavigation(path?: string, params?: Params) { + if (this.mobileApp) { + this.mobileChannel.callHandler(navigationHandler, path, params); + } + } + + private onWindowMessage(event: MessageEvent) { + if (event.data) { + let message: WindowMessage; + try { + message = JSON.parse(event.data); + } catch (e) {} + if (message && message.type) { + switch (message.type) { + case 'openDashboardMessage': + const openDashboardMessage: OpenDashboardMessage = message.data; + this.openDashboard(openDashboardMessage); + break; + case 'reloadUserMessage': + const reloadUserMessage: ReloadUserMessage = message.data; + this.reloadUser(reloadUserMessage); + break; + case 'toggleDashboardLayout': + if (this.toggleLayoutFunction) { + this.toggleLayoutFunction(); + } + break; + } + } + } + } + + private openDashboard(openDashboardMessage: OpenDashboardMessage) { + if (openDashboardMessage && openDashboardMessage.dashboardId) { + if (this.reloadUserObservable) { + this.reloadUserObservable.subscribe( + (authenticated) => { + if (authenticated) { + this.doDashboardNavigation(openDashboardMessage); + } + } + ); + } else { + this.doDashboardNavigation(openDashboardMessage); + } + } + } + + private doDashboardNavigation(openDashboardMessage: OpenDashboardMessage) { + let url = `/dashboard/${openDashboardMessage.dashboardId}`; + const params = []; + if (openDashboardMessage.state) { + params.push(`state=${openDashboardMessage.state}`); + } + if (openDashboardMessage.embedded) { + params.push(`embedded=true`); + } + if (openDashboardMessage.hideToolbar) { + params.push(`hideToolbar=true`); + } + if (this.lastDashboardId === openDashboardMessage.dashboardId) { + params.push(`reload=${new Date().getTime()}`); + } + if (params.length) { + url += `?${params.join('&')}`; + } + this.lastDashboardId = openDashboardMessage.dashboardId; + this.router.navigateByUrl(url, {replaceUrl: true}); + } + + private reloadUser(reloadUserMessage: ReloadUserMessage) { + if (reloadUserMessage && reloadUserMessage.accessToken && reloadUserMessage.refreshToken) { + this.reloadUserObservable = this.authService.setUserFromJwtToken(reloadUserMessage.accessToken, + reloadUserMessage.refreshToken, true).pipe( + tap( + () => { + this.reloadUserObservable = null; + } + ) + ); + } + } + } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index 6291438a58..a287802ec2 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -441,3 +441,7 @@ export function generateSecret(length?: number): string { export function validateEntityId(entityId: EntityId | null): boolean { return isDefinedAndNotNull(entityId?.id) && entityId.id !== NULL_UUID && isDefinedAndNotNull(entityId?.entityType); } + +export function isMobileApp(): boolean { + return isDefined((window as any).flutter_inappwebview); +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 5c54fe23e0..8ff6944856 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -87,7 +87,7 @@ (click)="updateDashboardImage($event)"> wallpaper - + + + diff --git a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts index 40a6b0cece..df164adda4 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts @@ -43,7 +43,7 @@ import { TranslateService } from '@ngx-translate/core'; import { BaseData, HasId } from '@shared/models/base-data'; import { ActivatedRoute } from '@angular/router'; import { - CellActionDescriptor, + CellActionDescriptor, CellActionDescriptorType, EntityActionTableColumn, EntityColumn, EntityTableColumn, @@ -104,6 +104,8 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn timewindow: Timewindow; dataSource: EntitiesDataSource>; + cellActionType = CellActionDescriptorType; + isDetailsOpen = false; detailsPanelOpened = new EventEmitter(); diff --git a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts index cba32ddcaf..d6378620f9 100644 --- a/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts @@ -280,7 +280,7 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV editingEntity.additionalInfo = mergeDeep((this.editingEntity as any).additionalInfo, this.entityComponent.entityFormValue()?.additionalInfo); } - this.entitiesTableConfig.saveEntity(editingEntity).subscribe( + this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity).subscribe( (entity) => { this.entity = entity; this.entityComponent.entity = entity; diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html index 17caf4f0d7..20f271d660 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.html @@ -66,4 +66,5 @@ {{ 'device-profile.device-profile-required' | translate }} + {{ hint | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts index 3a0ae491da..a2fa8d1ed3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-autocomplete.component.ts @@ -91,6 +91,9 @@ export class DeviceProfileAutocompleteComponent implements ControlValueAccessor, @Input() disabled: boolean; + @Input() + hint: string; + @Output() deviceProfileUpdated = new EventEmitter(); diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts index 0efcad9608..58433f01c4 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile-dialog.component.ts @@ -90,7 +90,7 @@ export class DeviceProfileDialogComponent extends this.submitted = true; if (this.deviceProfileComponent.entityForm.valid) { this.deviceProfile = {...this.deviceProfile, ...this.deviceProfileComponent.entityFormValue()}; - this.deviceProfileService.saveDeviceProfile(this.deviceProfile).subscribe( + this.deviceProfileService.saveDeviceProfileAndConfirmOtaChange(this.deviceProfile, this.deviceProfile).subscribe( (deviceProfile) => { this.dialogRef.close(deviceProfile); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 325bc20419..5fa6c1acdb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -67,18 +67,18 @@ [queueType]="serviceType" formControlName="defaultQueueName"> - - - + - + device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index 6db89d818e..efaa234adb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -40,7 +40,7 @@ import { EntityType } from '@shared/models/entity-type.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { ServiceType } from '@shared/models/queue.models'; import { EntityId } from '@shared/models/id/entity-id'; -import { FirmwareType } from '@shared/models/firmware.models'; +import { OtaUpdateType } from '@shared/models/ota-package.models'; import { DashboardId } from '@shared/models/id/dashboard-id'; @Component({ @@ -71,7 +71,7 @@ export class DeviceProfileComponent extends EntityComponent { deviceProfileId: EntityId; - firmwareTypes = FirmwareType; + otaUpdateType = OtaUpdateType; constructor(protected store: Store, protected translate: TranslateService, diff --git a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts index dc1da3d973..996e79d6bc 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; @@ -33,6 +33,8 @@ import { transportPayloadTypeTranslationMap, } from '@shared/models/device.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-coap-device-profile-transport-configuration', @@ -44,7 +46,7 @@ import { isDefinedAndNotNull } from '@core/utils'; multi: true }] }) -export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class CoapDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { coapTransportDeviceTypes = Object.keys(CoapTransportDeviceType); @@ -56,6 +58,7 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control coapDeviceProfileTransportConfigurationFormGroup: FormGroup; + private destroy$ = new Subject(); private requiredValue: boolean; private transportPayloadTypeConfiguration = this.fb.group({ @@ -99,15 +102,23 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control }) } ); - this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType') - .valueChanges.subscribe(coapDeviceType => { + this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(coapDeviceType => { this.updateCoapDeviceTypeBasedControls(coapDeviceType, true); }); - this.coapDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.coapDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + get coapDeviceTypeDefault(): boolean { const coapDeviceType = this.coapDeviceProfileTransportConfigurationFormGroup.get('coapDeviceTypeConfiguration.coapDeviceType').value; return coapDeviceType === CoapTransportDeviceType.DEFAULT; diff --git a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts index b95433d096..27dda6dc91 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-configuration.component.ts @@ -14,13 +14,15 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProfileConfiguration, DeviceProfileType } from '@shared/models/device.models'; import { deepClone } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-device-profile-configuration', @@ -32,12 +34,14 @@ import { deepClone } from '@core/utils'; multi: true }] }) -export class DeviceProfileConfigurationComponent implements ControlValueAccessor, OnInit { +export class DeviceProfileConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { deviceProfileType = DeviceProfileType; deviceProfileConfigurationFormGroup: FormGroup; + private destroy$ = new Subject(); + private requiredValue: boolean; get required(): boolean { return this.requiredValue; @@ -69,11 +73,18 @@ export class DeviceProfileConfigurationComponent implements ControlValueAccessor this.deviceProfileConfigurationFormGroup = this.fb.group({ configuration: [null, Validators.required] }); - this.deviceProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.deviceProfileConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html index fdc02f910a..711b72d615 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html @@ -15,112 +15,107 @@ limitations under the License. --> -

-
-
-
- - {{ 'device-profile.lwm2m.mode' | translate }} - - - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} - - - - - {{ 'device-profile.lwm2m.server-host' | translate }} - - - {{ 'device-profile.lwm2m.server-host' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.server-port' | translate }} - - - {{ 'device-profile.lwm2m.server-port' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.short-id' | translate }} - - - {{ 'device-profile.lwm2m.short-id' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - -
-
-
-
- - {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} - - - {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} - - - {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - - {{ 'device-profile.lwm2m.bootstrap-server' | translate }} - -
-
- - {{ 'device-profile.lwm2m.server-public-key' | translate }} - - {{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}} - - {{ 'device-profile.lwm2m.server-public-key' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - {{ 'device-profile.lwm2m.server-public-key' | translate }} - {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { - count: 0} }} - - - {{ 'device-profile.lwm2m.server-public-key' | translate }} - {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { - count: lenMaxServerPublicKey } }} - - -
-
+
+
+ + {{ 'device-profile.lwm2m.mode' | translate }} + + + {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} + + + + + {{ 'device-profile.lwm2m.server-host' | translate }} + + + {{ 'device-profile.lwm2m.server-host' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.server-port' | translate }} + + + {{ 'device-profile.lwm2m.server-port' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.short-id' | translate }} + + + {{ 'device-profile.lwm2m.short-id' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + +
+
+ + {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} + + + {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} + + + {{ 'device-profile.lwm2m.bootstrap-server-account-timeout' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + + {{ 'device-profile.lwm2m.bootstrap-server' | translate }} + +
+
+
+ + {{ 'device-profile.lwm2m.server-public-key' | translate }} + + {{serverPublicKey.value?.length || 0}}/{{lenMaxServerPublicKey}} + + {{ 'device-profile.lwm2m.server-public-key' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + + {{ 'device-profile.lwm2m.server-public-key' | translate }} + {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { + count: 0} }} + + + {{ 'device-profile.lwm2m.server-public-key' | translate }} + {{ 'device-profile.lwm2m.pattern_hex_dec' | translate: { + count: lenMaxServerPublicKey } }} + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html index a29605a527..e75aedd47c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html @@ -20,20 +20,6 @@
-
- - {{ 'device-profile.lwm2m.client-only-observe-after-connect-label' | translate }} - - {{ 'device-profile.lwm2m.client-only-observe-after-connect' | translate: - {count: 1} }} - {{ 'device-profile.lwm2m.client-only-observe-after-connect' | translate: - {count: 2} }} - - -
- +
@@ -153,6 +139,104 @@
+ + +
+ +
+ + + +
{{ 'device-profile.lwm2m.client-strategy' | translate | uppercase }}
+
+
+ +
+ + {{ 'device-profile.lwm2m.client-strategy-label' | translate }} + + {{ 'device-profile.lwm2m.client-strategy-connect' | translate: + {count: 1} }} + {{ 'device-profile.lwm2m.client-strategy-connect' | translate: + {count: 2} }} + + +
+
+
+
+ + + +
{{ 'device-profile.lwm2m.ota-update-strategy' | translate | uppercase }}
+
+
+ +
+ + {{ 'device-profile.lwm2m.fw-update-strategy-label' | translate }} + + {{ 'device-profile.lwm2m.fw-update-strategy' | translate: + {count: 1} }} + {{ 'device-profile.lwm2m.fw-update-strategy' | translate: + {count: 2} }} + {{ 'device-profile.lwm2m.fw-update-strategy' | translate: + {count: 3} }} + + + + {{ 'device-profile.lwm2m.sw-update-strategy-label' | translate }} + + {{ 'device-profile.lwm2m.sw-update-strategy' | translate: + {count: 1} }} + {{ 'device-profile.lwm2m.sw-update-strategy' | translate: + {count: 2} }} + + +
+
+
+ + + +
{{ 'device-profile.lwm2m.ota-update-recourse' | translate | uppercase }}
+
+
+ +
+
+ + {{ 'device-profile.lwm2m.fw-update-recourse' | translate }} + + + {{ 'device-profile.lwm2m.fw-update-recourse' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + +
+
+ + {{ 'device-profile.lwm2m.sw-update-recourse' | translate }} + + + {{ 'device-profile.lwm2m.sw-update-recourse' | translate }} + {{ 'device-profile.lwm2m.required' | translate }} + + +
+
+
+
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index 0a943ab5af..bc9b36271d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -15,13 +15,15 @@ /// import { DeviceProfileTransportConfiguration } from '@shared/models/device.models'; -import { Component, forwardRef, Input } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ATTRIBUTE, BINDING_MODE, BINDING_MODE_NAMES, + DEFAULT_FW_UPDATE_RESOURCE, + DEFAULT_SW_UPDATE_RESOURCE, getDefaultProfileConfig, Instance, INSTANCES, @@ -39,6 +41,9 @@ import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/util import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import { Direction } from '@shared/models/page/sort-order'; import _ from 'lodash'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { MatSelectChange } from '@angular/material/select'; @Component({ selector: 'tb-profile-lwm2m-device-transport-configuration', @@ -49,11 +54,12 @@ import _ from 'lodash'; multi: true }] }) -export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators { +export class Lwm2mDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, Validators, OnDestroy { private configurationValue: Lwm2mProfileConfigModels; private requiredValue: boolean; private disabled = false; + private destroy$ = new Subject(); bindingModeType = BINDING_MODE; bindingModeTypes = Object.keys(BINDING_MODE); @@ -64,6 +70,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro bootstrapServer: string; lwm2mServer: string; sortFunction: (key: string, value: object) => object; + isFwUpdateStrategy: boolean; + isSwUpdateStrategy: boolean; get required(): boolean { return this.requiredValue; @@ -80,24 +88,32 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro constructor(private fb: FormBuilder, private deviceProfileService: DeviceProfileService) { this.lwm2mDeviceProfileFormGroup = this.fb.group({ - clientOnlyObserveAfterConnect: [1, []], objectIds: [null, Validators.required], observeAttrTelemetry: [null, Validators.required], shortId: [null, Validators.required], lifetime: [null, Validators.required], defaultMinPeriod: [null, Validators.required], notifIfDisabled: [true, []], - binding:[], + binding: [], bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required], + clientStrategy: [1, []], + fwUpdateStrategy: [1, []], + swUpdateStrategy: [1, []], + fwUpdateRecourse: ['', []], + swUpdateRecourse: ['', []] }); this.lwm2mDeviceConfigFormGroup = this.fb.group({ configurationJson: [null, Validators.required] }); - this.lwm2mDeviceProfileFormGroup.valueChanges.subscribe((value) => { + this.lwm2mDeviceProfileFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { this.updateDeviceProfileValue(value); }); - this.lwm2mDeviceConfigFormGroup.valueChanges.subscribe(() => { + this.lwm2mDeviceConfigFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); this.sortFunction = this.sortObjectKeyPathJson; @@ -110,6 +126,11 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro registerOnTouched(fn: any): void { } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (isDisabled) { @@ -122,11 +143,17 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } writeValue(value: Lwm2mProfileConfigModels | null): void { - this.configurationValue = (Object.keys(value).length === 0) ? getDefaultProfileConfig() : value; - this.lwm2mDeviceConfigFormGroup.patchValue({ - configurationJson: this.configurationValue - }, {emitEvent: false}); - this.initWriteValue(); + if (isDefinedAndNotNull(value)) { + if (Object.keys(value).length !== 0 && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) { + this.configurationValue = value; + } else { + this.configurationValue = getDefaultProfileConfig(); + } + this.lwm2mDeviceConfigFormGroup.patchValue({ + configurationJson: this.configurationValue + }, {emitEvent: false}); + this.initWriteValue(); + } } private initWriteValue = (): void => { @@ -149,8 +176,13 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private updateWriteValue = (value: ModelValue): void => { + let fwResource = this.configurationValue.clientLwM2mSettings.fwUpdateStrategy === '2' && + isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.fwUpdateRecourse) ? + this.configurationValue.clientLwM2mSettings.fwUpdateRecourse : ''; + let swResource = this.configurationValue.clientLwM2mSettings.swUpdateStrategy === '2' && + isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.fwUpdateRecourse) ? + this.configurationValue.clientLwM2mSettings.swUpdateRecourse : ''; this.lwm2mDeviceProfileFormGroup.patchValue({ - clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, objectIds: value, observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value.objectsList), shortId: this.configurationValue.bootstrap.servers.shortId, @@ -159,9 +191,20 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro notifIfDisabled: this.configurationValue.bootstrap.servers.notifIfDisabled, binding: this.configurationValue.bootstrap.servers.binding, bootstrapServer: this.configurationValue.bootstrap.bootstrapServer, - lwm2mServer: this.configurationValue.bootstrap.lwm2mServer + lwm2mServer: this.configurationValue.bootstrap.lwm2mServer, + clientStrategy: this.configurationValue.clientLwM2mSettings.clientStrategy, + fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy, + swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy, + fwUpdateRecourse: fwResource, + swUpdateRecourse: swResource }, {emitEvent: false}); + this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = fwResource; + this.configurationValue.clientLwM2mSettings.swUpdateRecourse = swResource; + this.isFwUpdateStrategy = this.configurationValue.clientLwM2mSettings.fwUpdateStrategy === '2'; + this.isSwUpdateStrategy = this.configurationValue.clientLwM2mSettings.swUpdateStrategy === '2'; + this.otaUpdateSwStrategyValidate(); + this.otaUpdateFwStrategyValidate(); } private updateModel = (): void => { @@ -181,8 +224,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private updateDeviceProfileValue(config): void { if (this.lwm2mDeviceProfileFormGroup.valid) { - this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect = - config.clientOnlyObserveAfterConnect; this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry.clientLwM2M); this.configurationValue.bootstrap.bootstrapServer = config.bootstrapServer; this.configurationValue.bootstrap.lwm2mServer = config.lwm2mServer; @@ -192,6 +233,11 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro bootstrapServers.defaultMinPeriod = config.defaultMinPeriod; bootstrapServers.notifIfDisabled = config.notifIfDisabled; bootstrapServers.binding = config.binding; + this.configurationValue.clientLwM2mSettings.clientStrategy = config.clientStrategy; + this.configurationValue.clientLwM2mSettings.fwUpdateStrategy = config.fwUpdateStrategy; + this.configurationValue.clientLwM2mSettings.swUpdateStrategy = config.swUpdateStrategy; + this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = config.fwUpdateRecourse; + this.configurationValue.clientLwM2mSettings.swUpdateRecourse = config.swUpdateRecourse; this.upDateJsonAllConfig(); this.updateModel(); } @@ -241,7 +287,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro const pathParameter = Array.from(path.split('/'), String); const objectLwM2M = clientObserveAttrTelemetry.find(x => x.keyId === pathParameter[0]); if (objectLwM2M) { - const instance = this.updateInInstanceKeyName (objectLwM2M.instances[0], +pathParameter[1]); + const instance = this.updateInInstanceKeyName(objectLwM2M.instances[0], +pathParameter[1]); objectLwM2M.instances.push(instance); } }); @@ -252,7 +298,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro instanceUpdate.id = instanceId; instanceUpdate.resources.forEach(resource => { resource.keyName = _.camelCase(resource.name + instanceUpdate.id); - }) + }); return instanceUpdate; } @@ -466,4 +512,54 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } }); } + + changeFwUpdateStrategy($event: MatSelectChange) { + if ($event.value === '2') { + this.isFwUpdateStrategy = true; + if (isEmpty(this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').value)) { + this.lwm2mDeviceProfileFormGroup.patchValue({ + fwUpdateRecourse: DEFAULT_FW_UPDATE_RESOURCE + }, + {emitEvent: false}); + } + } else { + this.isFwUpdateStrategy = false; + } + this.otaUpdateFwStrategyValidate(); + } + + changeSwUpdateStrategy($event: MatSelectChange) { + if ($event.value === '2') { + this.isSwUpdateStrategy = true; + if (isEmpty(this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').value)) { + this.lwm2mDeviceProfileFormGroup.patchValue({ + swUpdateRecourse: DEFAULT_SW_UPDATE_RESOURCE + }, + {emitEvent: false}); + + } + } else { + this.isSwUpdateStrategy = false; + } + this.otaUpdateSwStrategyValidate(); + } + + private otaUpdateFwStrategyValidate(): void { + if (this.isFwUpdateStrategy) { + this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').setValidators([Validators.required]); + } else { + this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').clearValidators(); + } + this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').updateValueAndValidity(); + } + + private otaUpdateSwStrategyValidate(): void { + if (this.isSwUpdateStrategy) { + this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').setValidators([Validators.required]); + } else { + this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').clearValidators(); + } + this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').updateValueAndValidity(); + } + } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts index 8f1f676769..ae7c3ccdc6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts @@ -15,10 +15,19 @@ /// import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { Observable } from 'rxjs'; -import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; +import { distinctUntilChanged, filter, mergeMap, share, tap } from 'rxjs/operators'; import { ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT } from './lwm2m-profile-config.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { Direction } from '@shared/models/page/sort-order'; @@ -33,13 +42,18 @@ import { PageLink } from '@shared/models/page/page-link'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => Lwm2mObjectListComponent), multi: true - }] + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => Lwm2mObjectListComponent), + multi: true + } + ] }) -export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validators { +export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, Validator { private requiredValue: boolean; private dirty = false; - private lw2mModels: Observable>; private modelValue: Array = []; lwm2mListFormGroup: FormGroup; @@ -78,8 +92,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } private updateValidators = (): void => { - this.lwm2mListFormGroup.get('objectLwm2m').setValidators(this.required ? [Validators.required] : []); - this.lwm2mListFormGroup.get('objectLwm2m').updateValueAndValidity(); + this.lwm2mListFormGroup.get('objectsList').setValidators(this.required ? [Validators.required] : []); + this.lwm2mListFormGroup.get('objectsList').updateValueAndValidity(); } registerOnChange(fn: any): void { @@ -92,6 +106,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V ngOnInit() { this.filteredObjectsList = this.lwm2mListFormGroup.get('objectLwm2m').valueChanges .pipe( + distinctUntilChanged(), tap((value) => { if (value && typeof value !== 'string') { this.add(value); @@ -100,7 +115,8 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } }), filter(searchText => isString(searchText)), - mergeMap(searchText => this.fetchListObjects(searchText)) + mergeMap(searchText => this.fetchListObjects(searchText)), + share() ); } @@ -131,6 +147,12 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } } + validate(): ValidationErrors | null { + return this.lwm2mListFormGroup.valid ? null : { + lwm2mListObj: false + }; + } + private add(object: ObjectLwM2M): void { if (isDefinedAndNotNull(this.modelValue) && this.modelValue.indexOf(object.keyId) === -1) { this.modelValue.push(object.keyId); @@ -157,23 +179,13 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V return object ? object.name : undefined; } - private fetchListObjects = (searchText?: string): Observable> => { + private fetchListObjects = (searchText: string): Observable> => { this.searchText = searchText; - return this.getLwM2mModelsPage().pipe( - map(objectLwM2Ms => objectLwM2Ms) - ); - } - - private getLwM2mModelsPage(): Observable> { const pageLink = new PageLink(PAGE_SIZE_LIMIT, 0, this.searchText, { property: 'id', direction: Direction.ASC }); - this.lw2mModels = this.deviceProfileService.getLwm2mObjectsPage(pageLink).pipe( - publishReplay(1), - refCount() - ); - return this.lw2mModels; + return this.deviceProfileService.getLwm2mObjectsPage(pageLink); } onFocus = (): void => { @@ -183,10 +195,9 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } } - private clear = (value: string = ''): void => { - this.objectInput.nativeElement.value = value; + private clear = (): void => { this.searchText = ''; - this.lwm2mListFormGroup.get('objectLwm2m').patchValue(value); + this.lwm2mListFormGroup.get('objectLwm2m').patchValue(null); setTimeout(() => { this.objectInput.nativeElement.blur(); this.objectInput.nativeElement.focus(); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html index e58861c1eb..3ee9bffa27 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
+
@@ -46,14 +46,14 @@
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html index 759e880ffa..02d594905d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html @@ -16,7 +16,7 @@ -->
- + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss index 2de6fe17d6..afed0db1ff 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss @@ -24,6 +24,9 @@ } :host{ + section { + padding: 2px; + } .instance-list { mat-expansion-panel-header { color: inherit; diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index acea41394a..ff8225a27d 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -28,7 +28,7 @@ export const TELEMETRY = 'telemetry'; export const KEY_NAME = 'keyName'; export const DEFAULT_ID_SERVER = 123; export const DEFAULT_ID_BOOTSTRAP = 111; -export const DEFAULT_HOST_NAME = 'localhost'; +export const DEFAULT_LOCAL_HOST_NAME = 'localhost'; export const DEFAULT_PORT_SERVER_NO_SEC = 5685; export const DEFAULT_PORT_BOOTSTRAP_NO_SEC = 5687; export const DEFAULT_CLIENT_HOLD_OFF_TIME = 1; @@ -43,6 +43,9 @@ export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/; export const INSTANCES_ID_VALUE_MIN = 0; export const INSTANCES_ID_VALUE_MAX = 65535; +export const DEFAULT_OTA_UPDATE_PROTOCOL = "coap://"; +export const DEFAULT_FW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ":"+ DEFAULT_PORT_SERVER_NO_SEC; +export const DEFAULT_SW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ":"+ DEFAULT_PORT_SERVER_NO_SEC; export enum BINDING_MODE { @@ -64,7 +67,7 @@ export const BINDING_MODE_NAMES = new Map( [BINDING_MODE.UQ, 'UQ: UDP connection in queue mode'], [BINDING_MODE.US, 'US: both UDP and SMS connections active, both in standard mode'], [BINDING_MODE.UQS, 'UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode'], - [BINDING_MODE.T,'T: TCP connection in standard mode'], + [BINDING_MODE.T, 'T: TCP connection in standard mode'], [BINDING_MODE.TQ, 'TQ: TCP connection in queue mode'], [BINDING_MODE.TS, 'TS: both TCP and SMS connections active, both in standard mode'], [BINDING_MODE.TQS, 'TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode'], @@ -162,11 +165,14 @@ export interface Lwm2mProfileConfigModels { clientLwM2mSettings: ClientLwM2mSettings; observeAttr: ObservableAttributes; bootstrap: BootstrapSecurityConfig; - } export interface ClientLwM2mSettings { - clientOnlyObserveAfterConnect: number; + clientStrategy: string; + fwUpdateStrategy: string; + swUpdateStrategy: string; + fwUpdateRecourse: string; + swUpdateRecourse: string; } export interface ObservableAttributes { @@ -230,13 +236,17 @@ export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModel return { clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(), observeAttr: getDefaultProfileObserveAttrConfig(), - bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_HOST_NAME) + bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_LOCAL_HOST_NAME) }; } function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { return { - clientOnlyObserveAfterConnect: 1 + clientStrategy: "1", + fwUpdateStrategy: "1", + swUpdateStrategy: "1", + fwUpdateRecourse: DEFAULT_FW_UPDATE_RESOURCE, + swUpdateRecourse: DEFAULT_SW_UPDATE_RESOURCE }; } 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 381e2f9e8d..0cbb50ed8e 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 @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -39,6 +39,8 @@ import { transportPayloadTypeTranslationMap } from '@shared/models/device.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-mqtt-device-profile-transport-configuration', @@ -50,7 +52,7 @@ import { isDefinedAndNotNull } from '@core/utils'; multi: true }] }) -export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class MqttDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { transportPayloadTypes = Object.keys(TransportPayloadType); @@ -58,6 +60,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control mqttDeviceProfileTransportConfigurationFormGroup: FormGroup; + private destroy$ = new Subject(); private requiredValue: boolean; get required(): boolean { @@ -98,15 +101,23 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control }) }, {validator: this.uniqueDeviceTopicValidator} ); - this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType') - .valueChanges.subscribe(payloadType => { + this.mqttDeviceProfileTransportConfigurationFormGroup.get('transportPayloadTypeConfiguration.transportPayloadType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(payloadType => { this.updateTransportPayloadBasedControls(payloadType, true); }); - this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { @@ -192,8 +203,8 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control } private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null { - if (control.value) { - const formValue = control.value as MqttDeviceProfileTransportConfiguration; + if (control.getRawValue()) { + const formValue = control.getRawValue() as MqttDeviceProfileTransportConfiguration; if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) { return {unique: true}; } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts index 96f7454cba..e6749a9219 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts @@ -14,17 +14,19 @@ /// limitations under the License. /// -import {Component, forwardRef, Input, OnInit} from '@angular/core'; -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; -import {Store} from '@ngrx/store'; -import {AppState} from '@app/core/core.state'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProfileTransportConfiguration, DeviceTransportType, SnmpDeviceProfileTransportConfiguration } from '@shared/models/device.models'; -import {isDefinedAndNotNull} from "@core/utils"; +import { isDefinedAndNotNull } from '@core/utils'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; export interface OidMappingConfiguration { isAttribute: boolean; @@ -44,8 +46,11 @@ export interface OidMappingConfiguration { multi: true }] }) -export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { + snmpDeviceProfileTransportConfigurationFormGroup: FormGroup; + + private destroy$ = new Subject(); private requiredValue: boolean; private configuration = []; @@ -71,11 +76,18 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({ configuration: [null, Validators.required] }); - this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(() => { this.updateModel(); }); } + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + registerOnChange(fn: any): void { this.propagateChange = fn; } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 13e3976299..4dd248a10c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -88,6 +88,30 @@ {{ 'tenant-profile.maximum-rule-chains-range' | translate}} + + tenant-profile.maximum-resources-sum-data-size + + + {{ 'tenant-profile.maximum-resources-sum-data-size-required' | translate}} + + + {{ 'tenant-profile.maximum-resources-sum-data-size-range' | translate}} + + + + tenant-profile.maximum-ota-packages-sum-data-size + + + {{ 'tenant-profile.maximum-ota-packages-sum-data-size-required' | translate}} + + + {{ 'tenant-profile.maximum-ota-packages-sum-data-size-range' | translate}} + + tenant-profile.max-transport-messages + + tenant-profile.alarms-ttl-days + + + {{ 'tenant-profile.alarms-ttl-days-required' | translate}} + + + {{ 'tenant-profile.alarms-ttl-days-days-range' | translate}} + + tenant-profile.max-rule-node-executions-per-message { this.updateModel(); diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 245cb6503b..39be34b823 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -155,7 +155,7 @@ {{ 'device.credentials' | translate }} - {{ 'device.wizard.add-credential' | translate }} + {{ 'device.wizard.add-credentials' | translate }} diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index e6de5dbf84..a1393b0bd2 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -37,7 +37,7 @@ export type EntityStringFunction> = (entity: T) => str export type EntityVoidFunction> = (entity: T) => void; export type EntityIdsVoidFunction> = (ids: HasUUID[]) => void; export type EntityCountStringFunction = (count: number) => string; -export type EntityTwoWayOperation> = (entity: T) => Observable; +export type EntityTwoWayOperation> = (entity: T, originalEntity?: T) => Observable; export type EntityByIdOperation> = (id: HasUUID) => Observable; export type EntityIdOneWayOperation = (id: HasUUID) => Observable; export type EntityActionFunction> = (action: EntityAction) => boolean; @@ -48,6 +48,9 @@ export type CellContentFunction> = (entity: T, key: st export type CellTooltipFunction> = (entity: T, key: string) => string | undefined; export type HeaderCellStyleFunction> = (key: string) => object; export type CellStyleFunction> = (entity: T, key: string) => object; +export type CopyCellContent> = (entity: T, key: string, length: number) => object; + +export enum CellActionDescriptorType { 'DEFAULT', 'COPY_BUTTON'} export interface CellActionDescriptor> { name: string; @@ -56,7 +59,8 @@ export interface CellActionDescriptor> { mdiIcon?: string; style?: any; isEnabled: (entity: T) => boolean; - onAction: ($event: MouseEvent, entity: T) => void; + onAction: ($event: MouseEvent, entity: T) => any; + type?: CellActionDescriptorType; } export interface GroupActionDescriptor> { @@ -95,7 +99,8 @@ export class EntityTableColumn> extends BaseEntityTabl public sortable: boolean = true, public headerCellStyleFunction: HeaderCellStyleFunction = () => ({}), public cellTooltipFunction: CellTooltipFunction = () => undefined, - public isNumberColumn: boolean = false) { + public isNumberColumn: boolean = false, + public actionCell: CellActionDescriptor = null) { super('content', key, title, width, sortable); } } @@ -173,7 +178,7 @@ export class EntityTableConfig, P extends PageLink = P deleteEntitiesTitle: EntityCountStringFunction = () => ''; deleteEntitiesContent: EntityCountStringFunction = () => ''; loadEntity: EntityByIdOperation = () => of(); - saveEntity: EntityTwoWayOperation = (entity) => of(entity); + saveEntity: EntityTwoWayOperation = (entity, originalEntity) => of(entity); deleteEntity: EntityIdOneWayOperation = () => of(); entitiesFetchFunction: EntitiesFetchFunction = () => of(emptyPageData()); onEntityAction: EntityActionFunction = () => false; diff --git a/ui-ngx/src/app/modules/home/models/services.map.ts b/ui-ngx/src/app/modules/home/models/services.map.ts index c518249b77..4b65083038 100644 --- a/ui-ngx/src/app/modules/home/models/services.map.ts +++ b/ui-ngx/src/app/modules/home/models/services.map.ts @@ -35,7 +35,7 @@ import { Router } from '@angular/router'; import { BroadcastService } from '@core/services/broadcast.service'; import { ImportExportService } from '@home/components/import-export/import-export.service'; import { DeviceProfileService } from '@core/http/device-profile.service'; -import { FirmwareService } from '@core/http/firmware.service'; +import { OtaPackageService } from '@core/http/ota-package.service'; export const ServicesMap = new Map>( [ @@ -59,6 +59,6 @@ export const ServicesMap = new Map>( ['router', Router], ['importExport', ImportExportService], ['deviceProfileService', DeviceProfileService], - ['firmwareService', FirmwareService] + ['otaPackageService', OtaPackageService] ] ); diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index 3998b4fc9a..3ea9a1ca11 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -34,19 +34,19 @@ {{ 'admin.oauth2.enable' | translate }}
- +
- + - {{ domainListTittle(domain) }} + {{ domainListTittle(oauth2ParamsInfo) }} + + + + + + + +
- - {{ 'admin.domain-name-unique' | translate }} - -
-
- - admin.oauth2.redirect-uri-template - - - - - - - -
+
+
+ +
+ + + + +
+
+ admin.oauth2.no-mobile-apps +
+
+
+
+
+ + admin.oauth2.mobile-package + + + + {{ 'admin.oauth2.mobile-package-unique' | translate }} + +
+ + admin.oauth2.mobile-callback-url-scheme + + +
+
+ - +
-
- -
-
+
+
-
-
-
- -
- - + + + +
admin.oauth2.providers
- @@ -140,8 +190,8 @@ @@ -467,7 +517,7 @@ diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss index e17d7c34e4..aeb401dedf 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.scss @@ -62,7 +62,7 @@ } } } - .domains-list{ + .domains-list, .apps-list { margin-bottom: 1.5em; .mat-form-field-suffix .mat-icon-button .mat-icon{ font-size: 24px; diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index e93ea168fe..39fb6ac6ff 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -18,8 +18,6 @@ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormArray, FormBuilder, FormGroup, ValidationErrors, Validators } from '@angular/forms'; import { ClientAuthenticationMethod, - ClientRegistration, - DomainInfo, DomainSchema, domainSchemaTranslations, MapperConfig, @@ -27,8 +25,10 @@ import { MapperConfigCustom, MapperConfigType, OAuth2ClientRegistrationTemplate, - OAuth2ClientsDomainParams, - OAuth2ClientsParams, + OAuth2DomainInfo, + OAuth2Info, OAuth2MobileInfo, + OAuth2ParamsInfo, + OAuth2RegistrationInfo, TenantNameStrategy } from '@shared/models/oauth2.models'; import { Store } from '@ngrx/store'; @@ -52,6 +52,20 @@ import { ActivatedRoute } from '@angular/router'; }) export class OAuth2SettingsComponent extends PageComponent implements OnInit, HasConfirmForm, OnDestroy { + constructor(protected store: Store, + private route: ActivatedRoute, + private oauth2Service: OAuth2Service, + private fb: FormBuilder, + private dialogService: DialogService, + private translate: TranslateService, + @Inject(WINDOW) private window: Window) { + super(store); + } + + get oauth2ParamsInfos(): FormArray { + return this.oauth2SettingsForm.get('oauth2ParamsInfos') as FormArray; + } + private URL_REGEXP = /^[A-Za-z][A-Za-z\d.+-]*:\/*(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?(?:\/[\w#!:.,?+=&%@\-/]*)?$/; private DOMAIN_AND_PORT_REGEXP = /^(?:\w+(?::\w+)?@)?[^\s/]+(?::\d+)?$/; private subscriptions: Subscription[] = []; @@ -77,7 +91,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha readonly separatorKeysCodes: number[] = [ENTER, COMMA]; oauth2SettingsForm: FormGroup; - auth2ClientsParams: OAuth2ClientsParams; + oauth2Info: OAuth2Info; clientAuthenticationMethods = Object.keys(ClientAuthenticationMethod); mapperConfigType = MapperConfigType; @@ -90,14 +104,14 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private loginProcessingUrl: string = this.route.snapshot.data.loginProcessingUrl; - constructor(protected store: Store, - private route: ActivatedRoute, - private oauth2Service: OAuth2Service, - private fb: FormBuilder, - private dialogService: DialogService, - private translate: TranslateService, - @Inject(WINDOW) private window: Window) { - super(store); + private static validateScope(control: AbstractControl): ValidationErrors | null { + const scope: string[] = control.value; + if (!scope || !scope.length) { + return { + required: true + }; + } + return null; } ngOnInit(): void { @@ -106,10 +120,10 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.oauth2Service.getOAuth2Template(), this.oauth2Service.getOAuth2Settings() ]).subscribe( - ([templates, auth2ClientsParams]) => { + ([templates, oauth2Info]) => { this.initTemplates(templates); - this.auth2ClientsParams = auth2ClientsParams; - this.initOAuth2Settings(this.auth2ClientsParams); + this.oauth2Info = oauth2Info; + this.initOAuth2Settings(this.oauth2Info); } ); } @@ -130,11 +144,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha this.templateProvider.sort(); } - get domainsParams(): FormArray { - return this.oauth2SettingsForm.get('domainsParams') as FormArray; - } - - private formBasicGroup(type: MapperConfigType, mapperConfigBasic?: MapperConfigBasic): FormGroup { + private formBasicGroup(mapperConfigBasic?: MapperConfigBasic): FormGroup { let tenantNamePattern; if (mapperConfigBasic?.tenantNamePattern) { tenantNamePattern = mapperConfigBasic.tenantNamePattern; @@ -173,16 +183,16 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha private buildOAuth2SettingsForm(): void { this.oauth2SettingsForm = this.fb.group({ - domainsParams: this.fb.array([]), + oauth2ParamsInfos: this.fb.array([]), enabled: [false] }); } - private initOAuth2Settings(auth2ClientsParams: OAuth2ClientsParams): void { - if (auth2ClientsParams) { - this.oauth2SettingsForm.patchValue({enabled: auth2ClientsParams.enabled}, {emitEvent: false}); - auth2ClientsParams.domainsParams.forEach((domain) => { - this.domainsParams.push(this.buildDomainsForm(domain)); + private initOAuth2Settings(oauth2Info: OAuth2Info): void { + if (oauth2Info) { + this.oauth2SettingsForm.patchValue({enabled: oauth2Info.enabled}, {emitEvent: false}); + oauth2Info.oauth2ParamsInfos.forEach((oauth2ParamsInfo) => { + this.oauth2ParamsInfos.push(this.buildOAuth2ParamsInfoForm(oauth2ParamsInfo)); }); } } @@ -201,8 +211,20 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return null; } + private uniquePkgNameValidator(control: FormGroup): { [key: string]: boolean } | null { + if (control.parent?.value) { + const pkgName = control.value.pkgName; + const mobileInfosList = control.parent.getRawValue() + .filter((mobileInfo) => mobileInfo.pkgName === pkgName); + if (mobileInfosList.length > 1) { + return {unique: true}; + } + } + return null; + } + public domainListTittle(control: AbstractControl): string { - const domainInfos = control.get('domainInfos').value as DomainInfo[]; + const domainInfos = control.get('domainInfos').value as OAuth2DomainInfo[]; if (domainInfos.length) { const domainList = new Set(); domainInfos.forEach((domain) => { @@ -213,41 +235,51 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return this.translate.instant('admin.oauth2.new-domain'); } - private buildDomainsForm(auth2ClientsDomainParams?: OAuth2ClientsDomainParams): FormGroup { - const formDomain = this.fb.group({ + private buildOAuth2ParamsInfoForm(oauth2ParamsInfo?: OAuth2ParamsInfo): FormGroup { + const formOAuth2Params = this.fb.group({ domainInfos: this.fb.array([], Validators.required), + mobileInfos: this.fb.array([]), clientRegistrations: this.fb.array([], Validators.required) }); - if (auth2ClientsDomainParams) { - auth2ClientsDomainParams.domainInfos.forEach((domain) => { - this.clientDomainInfos(formDomain).push(this.buildDomainForm(domain)); + if (oauth2ParamsInfo) { + oauth2ParamsInfo.domainInfos.forEach((domain) => { + this.domainInfos(formOAuth2Params).push(this.buildDomainInfoForm(domain)); + }); + oauth2ParamsInfo.mobileInfos.forEach((mobile) => { + this.mobileInfos(formOAuth2Params).push(this.buildMobileInfoForm(mobile)); }); - auth2ClientsDomainParams.clientRegistrations.forEach((registration) => { - this.clientDomainProviders(formDomain).push(this.buildProviderForm(registration)); + oauth2ParamsInfo.clientRegistrations.forEach((registration) => { + this.clientRegistrations(formOAuth2Params).push(this.buildRegistrationForm(registration)); }); } else { - this.clientDomainProviders(formDomain).push(this.buildProviderForm()); - this.clientDomainInfos(formDomain).push(this.buildDomainForm()); + this.clientRegistrations(formOAuth2Params).push(this.buildRegistrationForm()); + this.domainInfos(formOAuth2Params).push(this.buildDomainInfoForm()); } - return formDomain; + return formOAuth2Params; } - private buildDomainForm(domainInfo?: DomainInfo): FormGroup { - const domain = this.fb.group({ + private buildDomainInfoForm(domainInfo?: OAuth2DomainInfo): FormGroup { + return this.fb.group({ name: [domainInfo ? domainInfo.name : this.window.location.hostname, [ Validators.required, Validators.pattern(this.DOMAIN_AND_PORT_REGEXP)]], scheme: [domainInfo?.scheme ? domainInfo.scheme : DomainSchema.HTTPS, Validators.required] }, {validators: this.uniqueDomainValidator}); - return domain; } - private buildProviderForm(clientRegistration?: ClientRegistration): FormGroup { + private buildMobileInfoForm(mobileInfo?: OAuth2MobileInfo): FormGroup { + return this.fb.group({ + pkgName: [mobileInfo?.pkgName, [Validators.required]], + callbackUrlScheme: [mobileInfo?.callbackUrlScheme, [Validators.required]], + }, {validators: this.uniquePkgNameValidator}); + } + + private buildRegistrationForm(registration?: OAuth2RegistrationInfo): FormGroup { let additionalInfo = null; - if (isDefinedAndNotNull(clientRegistration?.additionalInfo)) { - additionalInfo = clientRegistration.additionalInfo; + if (isDefinedAndNotNull(registration?.additionalInfo)) { + additionalInfo = registration.additionalInfo; if (this.templateProvider.indexOf(additionalInfo.providerName) === -1) { additionalInfo.providerName = 'Custom'; } @@ -261,43 +293,43 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha additionalInfo: this.fb.group({ providerName: [additionalInfo?.providerName ? additionalInfo?.providerName : defaultProviderName, Validators.required] }), - loginButtonLabel: [clientRegistration?.loginButtonLabel ? clientRegistration.loginButtonLabel : null, Validators.required], - loginButtonIcon: [clientRegistration?.loginButtonIcon ? clientRegistration.loginButtonIcon : null], - clientId: [clientRegistration?.clientId ? clientRegistration.clientId : '', Validators.required], - clientSecret: [clientRegistration?.clientSecret ? clientRegistration.clientSecret : '', Validators.required], - accessTokenUri: [clientRegistration?.accessTokenUri ? clientRegistration.accessTokenUri : '', + loginButtonLabel: [registration?.loginButtonLabel ? registration.loginButtonLabel : null, Validators.required], + loginButtonIcon: [registration?.loginButtonIcon ? registration.loginButtonIcon : null], + clientId: [registration?.clientId ? registration.clientId : '', Validators.required], + clientSecret: [registration?.clientSecret ? registration.clientSecret : '', Validators.required], + accessTokenUri: [registration?.accessTokenUri ? registration.accessTokenUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - authorizationUri: [clientRegistration?.authorizationUri ? clientRegistration.authorizationUri : '', + authorizationUri: [registration?.authorizationUri ? registration.authorizationUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], - scope: this.fb.array(clientRegistration?.scope ? clientRegistration.scope : [], this.validateScope), - jwkSetUri: [clientRegistration?.jwkSetUri ? clientRegistration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], - userInfoUri: [clientRegistration?.userInfoUri ? clientRegistration.userInfoUri : '', + scope: this.fb.array(registration?.scope ? registration.scope : [], OAuth2SettingsComponent.validateScope), + jwkSetUri: [registration?.jwkSetUri ? registration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], + userInfoUri: [registration?.userInfoUri ? registration.userInfoUri : '', [Validators.required, Validators.pattern(this.URL_REGEXP)]], clientAuthenticationMethod: [ - clientRegistration?.clientAuthenticationMethod ? clientRegistration.clientAuthenticationMethod : ClientAuthenticationMethod.POST, + registration?.clientAuthenticationMethod ? registration.clientAuthenticationMethod : ClientAuthenticationMethod.POST, Validators.required], userNameAttributeName: [ - clientRegistration?.userNameAttributeName ? clientRegistration.userNameAttributeName : 'email', Validators.required], + registration?.userNameAttributeName ? registration.userNameAttributeName : 'email', Validators.required], mapperConfig: this.fb.group({ allowUserCreation: [ - isDefinedAndNotNull(clientRegistration?.mapperConfig?.allowUserCreation) ? - clientRegistration.mapperConfig.allowUserCreation : true + isDefinedAndNotNull(registration?.mapperConfig?.allowUserCreation) ? + registration.mapperConfig.allowUserCreation : true ], activateUser: [ - isDefinedAndNotNull(clientRegistration?.mapperConfig?.activateUser) ? clientRegistration.mapperConfig.activateUser : false + isDefinedAndNotNull(registration?.mapperConfig?.activateUser) ? registration.mapperConfig.activateUser : false ], type: [ - clientRegistration?.mapperConfig?.type ? clientRegistration.mapperConfig.type : MapperConfigType.BASIC, Validators.required + registration?.mapperConfig?.type ? registration.mapperConfig.type : MapperConfigType.BASIC, Validators.required ] } ) }); - if (clientRegistration) { - this.changeMapperConfigType(clientRegistrationFormGroup, clientRegistration.mapperConfig.type, clientRegistration.mapperConfig); + if (registration) { + this.changeMapperConfigType(clientRegistrationFormGroup, registration.mapperConfig.type, registration.mapperConfig); } else { this.changeMapperConfigType(clientRegistrationFormGroup, MapperConfigType.BASIC); this.setProviderDefaultValue(defaultProviderName, clientRegistrationFormGroup); @@ -315,16 +347,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha return clientRegistrationFormGroup; } - private validateScope(control: AbstractControl): ValidationErrors | null { - const scope: string[] = control.value; - if (!scope || !scope.length) { - return { - required: true - }; - } - return null; - } - private setProviderDefaultValue(provider: string, clientRegistration: FormGroup) { if (provider === 'Custom') { clientRegistration.reset(this.defaultProvider, {emitEvent: false}); @@ -355,7 +377,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } else { mapperConfig.removeControl('custom'); if (!mapperConfig.get('basic')) { - mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); + mapperConfig.addControl('basic', this.formBasicGroup(predefinedValue?.basic)); } if (type === MapperConfigType.GITHUB) { mapperConfig.get('basic.emailAttributeKey').disable(); @@ -370,7 +392,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha const setting = this.oauth2SettingsForm.getRawValue(); this.oauth2Service.saveOAuth2Settings(setting).subscribe( (oauth2Settings) => { - this.auth2ClientsParams = oauth2Settings; + this.oauth2Info = oauth2Settings; this.oauth2SettingsForm.patchValue(this.oauth2SettingsForm, {emitEvent: false}); this.oauth2SettingsForm.markAsUntouched(); this.oauth2SettingsForm.markAsPristine(); @@ -403,58 +425,62 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha controller.markAsDirty(); } - addDomain(): void { - this.domainsParams.push(this.buildDomainsForm()); + addOAuth2ParamsInfo(): void { + this.oauth2ParamsInfos.push(this.buildOAuth2ParamsInfoForm()); } - deleteDomain($event: Event, index: number): void { + deleteOAuth2ParamsInfo($event: Event, index: number): void { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - const domainName = this.domainListTittle(this.domainsParams.at(index)); + const domainName = this.domainListTittle(this.oauth2ParamsInfos.at(index)); this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-domain-title', {domainName: domainName || ''}), this.translate.instant('admin.oauth2.delete-domain-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - this.domainsParams.removeAt(index); - this.domainsParams.markAsTouched(); - this.domainsParams.markAsDirty(); + this.oauth2ParamsInfos.removeAt(index); + this.oauth2ParamsInfos.markAsTouched(); + this.oauth2ParamsInfos.markAsDirty(); } }); } - clientDomainProviders(control: AbstractControl): FormArray { + clientRegistrations(control: AbstractControl): FormArray { return control.get('clientRegistrations') as FormArray; } - clientDomainInfos(control: AbstractControl): FormArray { + domainInfos(control: AbstractControl): FormArray { return control.get('domainInfos') as FormArray; } - addProvider(control: AbstractControl): void { - this.clientDomainProviders(control).push(this.buildProviderForm()); + mobileInfos(control: AbstractControl): FormArray { + return control.get('mobileInfos') as FormArray; } - deleteProvider($event: Event, control: AbstractControl, index: number): void { + addRegistration(control: AbstractControl): void { + this.clientRegistrations(control).push(this.buildRegistrationForm()); + } + + deleteRegistration($event: Event, control: AbstractControl, index: number): void { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - const providerName = this.clientDomainProviders(control).at(index).get('additionalInfo.providerName').value; + const providerName = this.clientRegistrations(control).at(index).get('additionalInfo.providerName').value; this.dialogService.confirm( this.translate.instant('admin.oauth2.delete-registration-title', {name: providerName || ''}), this.translate.instant('admin.oauth2.delete-registration-text'), null, this.translate.instant('action.delete') ).subscribe((data) => { if (data) { - this.clientDomainProviders(control).removeAt(index); - this.clientDomainProviders(control).markAsTouched(); - this.clientDomainProviders(control).markAsDirty(); + this.clientRegistrations(control).removeAt(index); + this.clientRegistrations(control).markAsTouched(); + this.clientRegistrations(control).markAsDirty(); } }); } @@ -480,24 +506,41 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha } addDomainInfo(control: AbstractControl): void { - this.clientDomainInfos(control).push(this.buildDomainForm({ + this.domainInfos(control).push(this.buildDomainInfoForm({ name: '', scheme: DomainSchema.HTTPS })); } - removeDomain($event: Event, control: AbstractControl, index: number): void { + removeDomainInfo($event: Event, control: AbstractControl, index: number): void { + if ($event) { + $event.stopPropagation(); + $event.preventDefault(); + } + this.domainInfos(control).removeAt(index); + this.domainInfos(control).markAsTouched(); + this.domainInfos(control).markAsDirty(); + } + + addMobileInfo(control: AbstractControl): void { + this.mobileInfos(control).push(this.buildMobileInfoForm({ + pkgName: '', + callbackUrlScheme: '' + })); + } + + removeMobileInfo($event: Event, control: AbstractControl, index: number): void { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - this.clientDomainInfos(control).removeAt(index); - this.clientDomainInfos(control).markAsTouched(); - this.clientDomainInfos(control).markAsDirty(); + this.mobileInfos(control).removeAt(index); + this.mobileInfos(control).markAsTouched(); + this.mobileInfos(control).markAsDirty(); } redirectURI(control: AbstractControl, schema?: DomainSchema): string { - const domainInfo = control.value as DomainInfo; + const domainInfo = control.value as OAuth2DomainInfo; if (domainInfo.name !== '') { let protocol; if (isDefined(schema)) { diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts index 804b349cff..2c53f04986 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.ts @@ -32,7 +32,7 @@ import { }) export class DeviceProfileTabsComponent extends EntityTabsComponent { - deviceTransportTypes = Object.keys(DeviceTransportType); + deviceTransportTypes = Object.values(DeviceTransportType); deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts index 49a256dd55..98281c1f88 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts @@ -104,7 +104,8 @@ export class DeviceProfilesTableConfigResolver implements Resolve this.deviceProfileService.getDeviceProfiles(pageLink); this.config.loadEntity = id => this.deviceProfileService.getDeviceProfile(id.id); - this.config.saveEntity = deviceProfile => this.deviceProfileService.saveDeviceProfile(deviceProfile); + this.config.saveEntity = (deviceProfile, originDeviceProfile) => + this.deviceProfileService.saveDeviceProfileAndConfirmOtaChange(originDeviceProfile, deviceProfile); this.config.deleteEntity = id => this.deviceProfileService.deleteDeviceProfile(id.id); this.config.onEntityAction = action => this.onDeviceProfileAction(action); this.config.deleteEnabled = (deviceProfile) => deviceProfile && !deviceProfile.default; diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.html b/ui-ngx/src/app/modules/home/pages/device/device.component.html index f09437662a..2dd6486eeb 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.html @@ -101,18 +101,18 @@ device.label - - - + - + diff --git a/ui-ngx/src/app/modules/home/pages/device/device.component.ts b/ui-ngx/src/app/modules/home/pages/device/device.component.ts index d7f21fb2fc..1ed9f784f1 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.component.ts @@ -34,7 +34,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions' import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { Subject } from 'rxjs'; -import { FirmwareType } from '@shared/models/firmware.models'; +import { OtaUpdateType } from '@shared/models/ota-package.models'; @Component({ selector: 'tb-device', @@ -49,7 +49,7 @@ export class DeviceComponent extends EntityComponent { deviceScope: 'tenant' | 'customer' | 'customer_user' | 'edge'; - firmwareTypes = FirmwareType; + otaUpdateType = OtaUpdateType; constructor(protected store: Store, protected translate: TranslateService, diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts deleted file mode 100644 index 23efe2117b..0000000000 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts +++ /dev/null @@ -1,115 +0,0 @@ -/// -/// Copyright © 2016-2021 The Thingsboard Authors -/// -/// Licensed under the Apache License, Version 2.0 (the "License"); -/// you may not use this file except in compliance with the License. -/// You may obtain a copy of the License at -/// -/// http://www.apache.org/licenses/LICENSE-2.0 -/// -/// Unless required by applicable law or agreed to in writing, software -/// distributed under the License is distributed on an "AS IS" BASIS, -/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -/// See the License for the specific language governing permissions and -/// limitations under the License. -/// - -import { Injectable } from '@angular/core'; -import { Resolve } from '@angular/router'; -import { - DateEntityTableColumn, - EntityTableColumn, - EntityTableConfig -} from '@home/models/entity/entities-table-config.models'; -import { - ChecksumAlgorithmTranslationMap, - Firmware, - FirmwareInfo, - FirmwareTypeTranslationMap -} from '@shared/models/firmware.models'; -import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; -import { TranslateService } from '@ngx-translate/core'; -import { DatePipe } from '@angular/common'; -import { FirmwareService } from '@core/http/firmware.service'; -import { PageLink } from '@shared/models/page/page-link'; -import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component'; -import { EntityAction } from '@home/models/entity/entity-component.models'; -import { FileSizePipe } from '@shared/pipe/file-size.pipe'; - -@Injectable() -export class FirmwareTableConfigResolve implements Resolve> { - - private readonly config: EntityTableConfig = new EntityTableConfig(); - - constructor(private translate: TranslateService, - private datePipe: DatePipe, - private firmwareService: FirmwareService, - private fileSize: FileSizePipe) { - this.config.entityType = EntityType.FIRMWARE; - this.config.entityComponent = FirmwaresComponent; - this.config.entityTranslations = entityTypeTranslations.get(EntityType.FIRMWARE); - this.config.entityResources = entityTypeResources.get(EntityType.FIRMWARE); - - this.config.entityTitle = (firmware) => firmware ? firmware.title : ''; - - this.config.columns.push( - new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'firmware.title', '25%'), - new EntityTableColumn('version', 'firmware.version', '25%'), - new EntityTableColumn('type', 'firmware.type', '25%', entity => { - return this.translate.instant(FirmwareTypeTranslationMap.get(entity.type)); - }), - new EntityTableColumn('fileName', 'firmware.file-name', '25%'), - new EntityTableColumn('dataSize', 'firmware.file-size', '70px', entity => { - return this.fileSize.transform(entity.dataSize || 0); - }), - new EntityTableColumn('checksum', 'firmware.checksum', '540px', entity => { - return `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}`; - }, () => ({}), false) - ); - - this.config.cellActionDescriptors.push( - { - name: this.translate.instant('firmware.download'), - icon: 'file_download', - isEnabled: (firmware) => firmware.hasData, - onAction: ($event, entity) => this.exportFirmware($event, entity) - } - ); - - this.config.deleteEntityTitle = firmware => this.translate.instant('firmware.delete-firmware-title', - { firmwareTitle: firmware.title }); - this.config.deleteEntityContent = () => this.translate.instant('firmware.delete-firmware-text'); - this.config.deleteEntitiesTitle = count => this.translate.instant('firmware.delete-firmwares-title', {count}); - this.config.deleteEntitiesContent = () => this.translate.instant('firmware.delete-firmwares-text'); - - this.config.entitiesFetchFunction = pageLink => this.firmwareService.getFirmwares(pageLink); - this.config.loadEntity = id => this.firmwareService.getFirmwareInfo(id.id); - this.config.saveEntity = firmware => this.firmwareService.saveFirmware(firmware); - this.config.deleteEntity = id => this.firmwareService.deleteFirmware(id.id); - - this.config.onEntityAction = action => this.onFirmwareAction(action); - } - - resolve(): EntityTableConfig { - this.config.tableTitle = this.translate.instant('firmware.firmware'); - return this.config; - } - - exportFirmware($event: Event, firmware: FirmwareInfo) { - if ($event) { - $event.stopPropagation(); - } - this.firmwareService.downloadFirmware(firmware.id.id).subscribe(); - } - - onFirmwareAction(action: EntityAction): boolean { - switch (action.action) { - case 'uploadFirmware': - this.exportFirmware(action.event, action.entity); - return true; - } - return false; - } - -} diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html deleted file mode 100644 index b608604d2f..0000000000 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html +++ /dev/null @@ -1,134 +0,0 @@ - -
- - -
- - -
-
-
- -
- firmware.warning-after-save-no-edit -
- - firmware.title - - - {{ 'firmware.title-required' | translate }} - - - - firmware.version - - - {{ 'firmware.version-required' | translate }} - - -
-
- - firmware.type - - - - {{ firmwareTypeTranslationMap.get(firmwareType) | translate }} - - - - - -
-
- - firmware.checksum-algorithm - - - - {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} - - - - - firmware.checksum - - -
-
- - -
-
-
- - firmware.file-name - - - - firmware.file-size-bytes - - - - firmware.content-type - - -
-
-
- - firmware.description - - -
-
- -
diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index 050b71db83..834321fc49 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -35,7 +35,7 @@ import { modulesMap } from '../../common/modules-map'; import { DeviceProfileModule } from './device-profile/device-profile.module'; import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; import { EdgeModule } from '@home/pages/edge/edge.module'; -import { FirmwareModule } from '@home/pages/firmware/firmware.module'; +import { OtaUpdateModule } from '@home/pages/ota-update/ota-update.module'; @NgModule({ exports: [ @@ -55,7 +55,7 @@ import { FirmwareModule } from '@home/pages/firmware/firmware.module'; DashboardModule, AuditLogModule, ApiUsageModule, - FirmwareModule, + OtaUpdateModule, UserModule ], providers: [ diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts similarity index 78% rename from ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts rename to ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts index 688f3bfc1f..6945244c47 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-routing.module.ts @@ -18,22 +18,22 @@ import { RouterModule, Routes } from '@angular/router'; import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; import { Authority } from '@shared/models/authority.enum'; import { NgModule } from '@angular/core'; -import { FirmwareTableConfigResolve } from '@home/pages/firmware/firmware-table-config.resolve'; +import { OtaUpdateTableConfigResolve } from '@home/pages/ota-update/ota-update-table-config.resolve'; const routes: Routes = [ { - path: 'firmwares', + path: 'otaUpdates', component: EntitiesTableComponent, data: { auth: [Authority.TENANT_ADMIN], - title: 'firmware.firmware', + title: 'ota-update.ota-updates', breadcrumb: { - label: 'firmware.firmware', + label: 'ota-update.ota-updates', icon: 'memory' } }, resolve: { - entitiesTableConfig: FirmwareTableConfigResolve + entitiesTableConfig: OtaUpdateTableConfigResolve } } ]; @@ -42,7 +42,7 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [ - FirmwareTableConfigResolve + OtaUpdateTableConfigResolve ] }) -export class FirmwareRoutingModule{ } +export class OtaUpdateRoutingModule { } diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts new file mode 100644 index 0000000000..0f542bec8a --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update-table-config.resolve.ts @@ -0,0 +1,157 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { + CellActionDescriptorType, + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { + ChecksumAlgorithmTranslationMap, + OtaPackage, + OtaPackageInfo, + OtaUpdateTypeTranslationMap +} from '@shared/models/ota-package.models'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { OtaPackageService } from '@core/http/ota-package.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { OtaUpdateComponent } from '@home/pages/ota-update/ota-update.component'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { FileSizePipe } from '@shared/pipe/file-size.pipe'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; + +@Injectable() +export class OtaUpdateTableConfigResolve implements Resolve> { + + private readonly config: EntityTableConfig = + new EntityTableConfig(); + + constructor(private translate: TranslateService, + private datePipe: DatePipe, + private store: Store, + private otaPackageService: OtaPackageService, + private fileSize: FileSizePipe) { + this.config.entityType = EntityType.OTA_PACKAGE; + this.config.entityComponent = OtaUpdateComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.OTA_PACKAGE); + this.config.entityResources = entityTypeResources.get(EntityType.OTA_PACKAGE); + + this.config.entityTitle = (otaPackage) => otaPackage ? otaPackage.title : ''; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'ota-update.title', '20%'), + new EntityTableColumn('version', 'ota-update.version', '20%'), + new EntityTableColumn('type', 'ota-update.package-type', '20%', entity => { + return this.translate.instant(OtaUpdateTypeTranslationMap.get(entity.type)); + }), + new EntityTableColumn('url', 'ota-update.direct-url', '20%', entity => { + return entity.url ? (entity.url.length > 20 ? `${entity.url.slice(0, 20)}…` : entity.url) : ''; + }, () => ({}), true, () => ({}), () => undefined, false, + { + name: this.translate.instant('ota-update.copy-direct-url'), + icon: 'content_paste', + style: { + 'font-size': '16px', + color: 'rgba(0,0,0,.87)' + }, + isEnabled: (otaPackage) => !!otaPackage.url, + onAction: ($event, entity) => entity.url, + type: CellActionDescriptorType.COPY_BUTTON + }), + new EntityTableColumn('fileName', 'ota-update.file-name', '20%'), + new EntityTableColumn('dataSize', 'ota-update.file-size', '70px', entity => { + return entity.dataSize ? this.fileSize.transform(entity.dataSize) : ''; + }), + new EntityTableColumn('checksum', 'ota-update.checksum', '220px', entity => { + return entity.checksum ? this.checksumText(entity) : ''; + }, () => ({}), true, () => ({}), () => undefined, false, + { + name: this.translate.instant('ota-update.copy-checksum'), + icon: 'content_paste', + style: { + 'font-size': '16px', + color: 'rgba(0,0,0,.87)' + }, + isEnabled: (otaPackage) => !!otaPackage.checksum, + onAction: ($event, entity) => entity.checksum, + type: CellActionDescriptorType.COPY_BUTTON + }) + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('ota-update.download'), + icon: 'file_download', + isEnabled: (otaPackage) => otaPackage.hasData && !otaPackage.url, + onAction: ($event, entity) => this.exportPackage($event, entity) + } + ); + + this.config.deleteEntityTitle = otaPackage => this.translate.instant('ota-update.delete-ota-update-title', + { title: otaPackage.title }); + this.config.deleteEntityContent = () => this.translate.instant('ota-update.delete-ota-update-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('ota-update.delete-ota-updates-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('ota-update.delete-ota-updates-text'); + + this.config.entitiesFetchFunction = pageLink => this.otaPackageService.getOtaPackages(pageLink); + this.config.loadEntity = id => this.otaPackageService.getOtaPackageInfo(id.id); + this.config.saveEntity = otaPackage => this.otaPackageService.saveOtaPackage(otaPackage); + this.config.deleteEntity = id => this.otaPackageService.deleteOtaPackage(id.id); + + this.config.onEntityAction = action => this.onPackageAction(action); + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('ota-update.packages-repository'); + return this.config; + } + + exportPackage($event: Event, otaPackageInfo: OtaPackageInfo) { + if ($event) { + $event.stopPropagation(); + } + if (otaPackageInfo.url) { + window.open(otaPackageInfo.url, '_blank'); + } else { + this.otaPackageService.downloadOtaPackage(otaPackageInfo.id.id).subscribe(); + } + } + + checksumText(entity): string { + let text = `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}`; + if (text.length > 20) { + text = `${text.slice(0, 20)}…`; + } + return text; + } + + onPackageAction(action: EntityAction): boolean { + switch (action.action) { + case 'uploadPackage': + this.exportPackage(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html new file mode 100644 index 0000000000..0f2662a251 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html @@ -0,0 +1,165 @@ + +
+ + +
+ + + +
+
+
+
+
+
+ + ota-update.title + + + {{ 'ota-update.title-required' | translate }} + + + + ota-update.version + + + {{ 'ota-update.version-required' | translate }} + + +
+ + + + ota-update.package-type + + + {{ otaUpdateTypeTranslationMap.get(packageType) | translate }} + + + +
+
ota-update.warning-after-save-no-edit
+ + Upload binary file + Use external URL + +
+
+
+ + + + {{ 'ota-update.auto-generate-checksum' | translate }} + +
+
+ + ota-update.checksum-algorithm + + + {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} + + + + + ota-update.checksum + + ota-update.checksum-hint + +
+
+
+ + ota-update.file-name + + + + ota-update.file-size-bytes + + + + ota-update.content-type + + +
+
+
+
+ + ota-update.direct-url + + + ota-update.direct-url-required + + +
+
+ + ota-update.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts similarity index 57% rename from ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts rename to ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts index e2283a588f..1182e6c40a 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts @@ -25,33 +25,55 @@ import { EntityComponent } from '@home/components/entity/entity.component'; import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, - Firmware, - FirmwareType, - FirmwareTypeTranslationMap -} from '@shared/models/firmware.models'; + OtaPackage, + OtaUpdateType, + OtaUpdateTypeTranslationMap +} from '@shared/models/ota-package.models'; import { ActionNotificationShow } from '@core/notification/notification.actions'; +import { filter, takeUntil } from 'rxjs/operators'; +import { isNotEmptyStr } from '@core/utils'; @Component({ - selector: 'tb-firmware', - templateUrl: './firmwares.component.html' + selector: 'tb-ota-update', + templateUrl: './ota-update.component.html' }) -export class FirmwaresComponent extends EntityComponent implements OnInit, OnDestroy { +export class OtaUpdateComponent extends EntityComponent implements OnInit, OnDestroy { private destroy$ = new Subject(); checksumAlgorithms = Object.values(ChecksumAlgorithm); checksumAlgorithmTranslationMap = ChecksumAlgorithmTranslationMap; - firmwareTypes = Object.values(FirmwareType); - firmwareTypeTranslationMap = FirmwareTypeTranslationMap; + packageTypes = Object.values(OtaUpdateType); + otaUpdateTypeTranslationMap = OtaUpdateTypeTranslationMap; constructor(protected store: Store, protected translate: TranslateService, - @Inject('entity') protected entityValue: Firmware, - @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + @Inject('entity') protected entityValue: OtaPackage, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, public fb: FormBuilder) { super(store, fb, entityValue, entitiesTableConfigValue); } + ngOnInit() { + super.ngOnInit(); + this.entityForm.get('resource').valueChanges.pipe( + filter(() => this.isAdd), + takeUntil(this.destroy$) + ).subscribe((resource) => { + if (resource === 'file') { + this.entityForm.get('url').clearValidators(); + this.entityForm.get('file').setValidators(Validators.required); + this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); + this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); + } else { + this.entityForm.get('file').clearValidators(); + this.entityForm.get('url').setValidators(Validators.required); + this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); + this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); + } + }); + } + ngOnDestroy() { super.ngOnDestroy(); this.destroy$.next(); @@ -66,14 +88,16 @@ export class FirmwaresComponent extends EntityComponent implements OnI } } - buildForm(entity: Firmware): FormGroup { + buildForm(entity: OtaPackage): FormGroup { const form = this.fb.group({ title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], - type: [entity?.type ? entity.type : FirmwareType.FIRMWARE, [Validators.required]], - deviceProfileId: [entity ? entity.deviceProfileId : null], + type: [entity?.type ? entity.type : OtaUpdateType.FIRMWARE, Validators.required], + deviceProfileId: [entity ? entity.deviceProfileId : null, Validators.required], checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256], checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)], + url: [entity ? entity.url : ''], + resource: ['file'], additionalInfo: this.fb.group( { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], @@ -82,6 +106,7 @@ export class FirmwaresComponent extends EntityComponent implements OnI }); if (this.isAdd) { form.addControl('file', this.fb.control(null, Validators.required)); + form.addControl('generateChecksum', this.fb.control(true)); } else { form.addControl('fileName', this.fb.control(null)); form.addControl('dataSize', this.fb.control(null)); @@ -90,7 +115,7 @@ export class FirmwaresComponent extends EntityComponent implements OnI return form; } - updateForm(entity: Firmware) { + updateForm(entity: OtaPackage) { this.entityForm.patchValue({ title: entity.title, version: entity.version, @@ -101,16 +126,33 @@ export class FirmwaresComponent extends EntityComponent implements OnI fileName: entity.fileName, dataSize: entity.dataSize, contentType: entity.contentType, + url: entity.url, + resource: isNotEmptyStr(entity.url) ? 'url' : 'file', additionalInfo: { description: entity.additionalInfo ? entity.additionalInfo.description : '' } }); + if (!this.isAdd && this.entityForm.enabled) { + this.entityForm.disable({emitEvent: false}); + this.entityForm.get('additionalInfo').enable({emitEvent: false}); + } + } + + onPackageIdCopied() { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('ota-update.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); } - onFirmwareIdCopied() { + onPackageChecksumCopied() { this.store.dispatch(new ActionNotificationShow( { - message: this.translate.instant('firmware.idCopiedMessage'), + message: this.translate.instant('ota-update.checksum-copied-message'), type: 'success', duration: 750, verticalPosition: 'bottom', @@ -118,14 +160,20 @@ export class FirmwaresComponent extends EntityComponent implements OnI })); } - onFirmwareChecksumCopied() { + onPackageDirectUrlCopied() { this.store.dispatch(new ActionNotificationShow( { - message: this.translate.instant('firmware.checksum-copied-message'), + message: this.translate.instant('ota-update.checksum-copied-message'), type: 'success', duration: 750, verticalPosition: 'bottom', horizontalPosition: 'right' })); } + + prepareFormValue(formValue: any): any { + delete formValue.resource; + delete formValue.generateChecksum; + return super.prepareFormValue(formValue); + } } diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts similarity index 79% rename from ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts rename to ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts index ab1b8343b2..58beef85ec 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.module.ts @@ -18,18 +18,18 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@shared/shared.module'; import { HomeComponentsModule } from '@home/components/home-components.module'; -import { FirmwareRoutingModule } from '@home/pages/firmware/firmware-routing.module'; -import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component'; +import { OtaUpdateRoutingModule } from '@home/pages/ota-update/ota-update-routing.module'; +import { OtaUpdateComponent } from '@home/pages/ota-update/ota-update.component'; @NgModule({ declarations: [ - FirmwaresComponent + OtaUpdateComponent ], imports: [ CommonModule, SharedModule, HomeComponentsModule, - FirmwareRoutingModule + OtaUpdateRoutingModule ] }) -export class FirmwareModule { } +export class OtaUpdateModule { } diff --git a/ui-ngx/src/app/shared/components/button/copy-button.component.html b/ui-ngx/src/app/shared/components/button/copy-button.component.html new file mode 100644 index 0000000000..20dd7d06ed --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/copy-button.component.html @@ -0,0 +1,26 @@ + + diff --git a/ui-ngx/src/app/shared/components/button/copy-button.component.scss b/ui-ngx/src/app/shared/components/button/copy-button.component.scss new file mode 100644 index 0000000000..0e5f5de8d9 --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/copy-button.component.scss @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +:host { + .mat-icon-button{ + height: 32px; + width: 32px; + line-height: 32px; + .mat-icon.copied{ + color: #00C851 !important; + } + } + &:hover{ + .mat-icon{ + color: #28567E !important; + } + } +} diff --git a/ui-ngx/src/app/shared/components/button/copy-button.component.ts b/ui-ngx/src/app/shared/components/button/copy-button.component.ts new file mode 100644 index 0000000000..8d5d72efe3 --- /dev/null +++ b/ui-ngx/src/app/shared/components/button/copy-button.component.ts @@ -0,0 +1,95 @@ +/// +/// Copyright © 2016-2021 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { ChangeDetectorRef, Component, EventEmitter, Input, Output } from '@angular/core'; +import { ClipboardService } from 'ngx-clipboard'; +import { TooltipPosition } from '@angular/material/tooltip'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'tb-copy-button', + styleUrls: ['copy-button.component.scss'], + templateUrl: './copy-button.component.html' +}) +export class CopyButtonComponent { + + private copedIcon = ''; + private timer; + + copied = false; + + @Input() + copyText: string; + + @Input() + disabled = false; + + @Input() + mdiIcon: string; + + @Input() + icon: string; + + @Input() + tooltipText: string; + + @Input() + tooltipPosition: TooltipPosition; + + @Input() + style: {[key: string]: any} = {}; + + @Output() + successCopied = new EventEmitter(); + + constructor(private clipboardService: ClipboardService, + private translate: TranslateService, + private cd: ChangeDetectorRef) { + } + + copy($event: Event): void { + $event.stopPropagation(); + if (this.timer) { + clearTimeout(this.timer); + } + this.clipboardService.copy(this.copyText); + this.successCopied.emit(this.copyText); + this.copedIcon = 'done'; + this.copied = true; + this.timer = setTimeout(() => { + this.copedIcon = null; + this.copied = false; + this.cd.detectChanges(); + }, 1500); + } + + get iconSymbol(): string { + return this.copedIcon || this.icon; + } + + get mdiIconSymbol(): string { + return this.copedIcon ? '' : this.mdiIcon; + } + + get matTooltipText(): string { + return this.copied ? this.translate.instant('ota-update.copied') : this.tooltipText; + } + + get matTooltipPosition(): TooltipPosition { + return this.copied ? 'below' : this.tooltipPosition; + } + +} diff --git a/ui-ngx/src/app/shared/components/color-input.component.html b/ui-ngx/src/app/shared/components/color-input.component.html index fbf8aea853..a0d226f589 100644 --- a/ui-ngx/src/app/shared/components/color-input.component.html +++ b/ui-ngx/src/app/shared/components/color-input.component.html @@ -20,7 +20,7 @@ {{icon}} {{label}} -
+
diff --git a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html similarity index 62% rename from ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html rename to ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html index 7bb434e7d1..51c291006d 100644 --- a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html @@ -15,40 +15,41 @@ limitations under the License. --> - + - - - + #packageAutocomplete="matAutocomplete" + [displayWith]="displayPackageFn"> + + - +
- {{ notFoundFirmware | translate }} + {{ notFoundPackage | translate }}
- {{ translate.get(notMatchingFirmware, + {{ translate.get(notMatchingPackage, {entity: truncate.transform(searchText, true, 6, '...')}) | async }}
- + {{ requiredErrorText | translate }} + {{ hintText | translate }}
diff --git a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts similarity index 58% rename from ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts rename to ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts index a3aeafbd5e..bb3be21fa8 100644 --- a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.ts @@ -28,29 +28,29 @@ import { BaseData } from '@shared/models/base-data'; import { EntityService } from '@core/http/entity.service'; import { TruncatePipe } from '@shared/pipe/truncate.pipe'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; -import { FirmwareInfo, FirmwareType } from '@shared/models/firmware.models'; -import { FirmwareService } from '@core/http/firmware.service'; +import { OtaPackageInfo, OtaUpdateTranslation, OtaUpdateType } from '@shared/models/ota-package.models'; +import { OtaPackageService } from '@core/http/ota-package.service'; import { PageLink } from '@shared/models/page/page-link'; import { Direction } from '@shared/models/page/sort-order'; @Component({ - selector: 'tb-firmware-autocomplete', - templateUrl: './firmware-autocomplete.component.html', + selector: 'tb-ota-package-autocomplete', + templateUrl: './ota-package-autocomplete.component.html', styleUrls: [], providers: [{ provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => FirmwareAutocompleteComponent), + useExisting: forwardRef(() => OtaPackageAutocompleteComponent), multi: true }] }) -export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnInit { +export class OtaPackageAutocompleteComponent implements ControlValueAccessor, OnInit { - firmwareFormGroup: FormGroup; + otaPackageFormGroup: FormGroup; modelValue: string | EntityId | null; @Input() - type = FirmwareType.FIRMWARE; + type = OtaUpdateType.FIRMWARE; @Input() deviceProfileId: string; @@ -78,42 +78,24 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn @Input() disabled: boolean; - @ViewChild('firmwareInput', {static: true}) firmwareInput: ElementRef; - @ViewChild('firmwareInput', {read: MatAutocompleteTrigger}) firmwareAutocomplete: MatAutocompleteTrigger; + @ViewChild('packageInput', {static: true}) packageInput: ElementRef; - filteredFirmwares: Observable>; + filteredPackages: Observable>; searchText = ''; private dirty = false; - private firmwareTypeTranslation = new Map( - [ - [FirmwareType.FIRMWARE, { - label: 'firmware.firmware', - required: 'firmware.firmware-required', - noFound: 'firmware.no-firmware-text', - noMatching: 'firmware.no-firmware-matching' - }], - [FirmwareType.SOFTWARE, { - label: 'firmware.software', - required: 'firmware.software-required', - noFound: 'firmware.no-software-text', - noMatching: 'firmware.no-software-matching' - }] - ] - ); - private propagateChange = (v: any) => { }; constructor(private store: Store, public translate: TranslateService, public truncate: TruncatePipe, private entityService: EntityService, - private firmwareService: FirmwareService, + private otaPackageService: OtaPackageService, private fb: FormBuilder) { - this.firmwareFormGroup = this.fb.group({ - firmwareId: [null] + this.otaPackageFormGroup = this.fb.group({ + packageId: [null] }); } @@ -125,7 +107,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn } ngOnInit() { - this.filteredFirmwares = this.firmwareFormGroup.get('firmwareId').valueChanges + this.filteredPackages = this.otaPackageFormGroup.get('packageId').valueChanges .pipe( tap(value => { let modelValue; @@ -140,7 +122,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn } }), map(value => value ? (typeof value === 'string' ? value : value.title) : ''), - mergeMap(name => this.fetchFirmware(name)), + mergeMap(name => this.fetchPackages(name)), share() ); } @@ -149,7 +131,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn } getCurrentEntity(): BaseData | null { - const currentRuleChain = this.firmwareFormGroup.get('firmwareId').value; + const currentRuleChain = this.otaPackageFormGroup.get('packageId').value; if (currentRuleChain && typeof currentRuleChain !== 'string') { return currentRuleChain as BaseData; } else { @@ -160,9 +142,9 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { - this.firmwareFormGroup.disable({emitEvent: false}); + this.otaPackageFormGroup.disable({emitEvent: false}); } else { - this.firmwareFormGroup.enable({emitEvent: false}); + this.otaPackageFormGroup.enable({emitEvent: false}); } } @@ -173,21 +155,21 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn writeValue(value: string | EntityId | null): void { this.searchText = ''; if (value != null && value !== '') { - let firmwareId = ''; + let packageId = ''; if (typeof value === 'string') { - firmwareId = value; + packageId = value; } else if (value.entityType && value.id) { - firmwareId = value.id; + packageId = value.id; } - if (firmwareId !== '') { - this.entityService.getEntity(EntityType.FIRMWARE, firmwareId, {ignoreLoading: true, ignoreErrors: true}).subscribe( + if (packageId !== '') { + this.entityService.getEntity(EntityType.OTA_PACKAGE, packageId, {ignoreLoading: true, ignoreErrors: true}).subscribe( (entity) => { this.modelValue = this.useFullEntityId ? entity.id : entity.id.id; - this.firmwareFormGroup.get('firmwareId').patchValue(entity, {emitEvent: false}); + this.otaPackageFormGroup.get('packageId').patchValue(entity, {emitEvent: false}); }, () => { this.modelValue = null; - this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false}); if (value !== null) { this.propagateChange(this.modelValue); } @@ -195,25 +177,25 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn ); } else { this.modelValue = null; - this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false}); this.propagateChange(null); } } else { this.modelValue = null; - this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false}); } this.dirty = true; } onFocus() { if (this.dirty) { - this.firmwareFormGroup.get('firmwareId').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.otaPackageFormGroup.get('packageId').updateValueAndValidity({onlySelf: true, emitEvent: true}); this.dirty = false; } } reset() { - this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: false}); } updateView(value: string | null) { @@ -223,47 +205,51 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn } } - displayFirmwareFn(firmware?: FirmwareInfo): string | undefined { - return firmware ? `${firmware.title} (${firmware.version})` : undefined; + displayPackageFn(packageInfo?: OtaPackageInfo): string | undefined { + return packageInfo ? `${packageInfo.title} (${packageInfo.version})` : undefined; } - fetchFirmware(searchText?: string): Observable> { + fetchPackages(searchText?: string): Observable> { this.searchText = searchText; const pageLink = new PageLink(50, 0, searchText, { property: 'title', direction: Direction.ASC }); - return this.firmwareService.getFirmwaresInfoByDeviceProfileId(pageLink, this.deviceProfileId, this.type, - true, {ignoreLoading: true}).pipe( + return this.otaPackageService.getOtaPackagesInfoByDeviceProfileId(pageLink, this.deviceProfileId, this.type, + {ignoreLoading: true}).pipe( map((data) => data && data.data.length ? data.data : null) ); } clear() { - this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: true}); + this.otaPackageFormGroup.get('packageId').patchValue('', {emitEvent: true}); setTimeout(() => { - this.firmwareInput.nativeElement.blur(); - this.firmwareInput.nativeElement.focus(); + this.packageInput.nativeElement.blur(); + this.packageInput.nativeElement.focus(); }, 0); } get placeholderText(): string { - return this.labelText || this.firmwareTypeTranslation.get(this.type).label; + return this.labelText || OtaUpdateTranslation.get(this.type).label; } get requiredErrorText(): string { - return this.requiredText || this.firmwareTypeTranslation.get(this.type).required; + return this.requiredText || OtaUpdateTranslation.get(this.type).required; + } + + get notFoundPackage(): string { + return OtaUpdateTranslation.get(this.type).noFound; } - get notFoundFirmware(): string { - return this.firmwareTypeTranslation.get(this.type).noFound; + get notMatchingPackage(): string { + return OtaUpdateTranslation.get(this.type).noMatching; } - get notMatchingFirmware(): string { - return this.firmwareTypeTranslation.get(this.type).noMatching; + get hintText(): string { + return OtaUpdateTranslation.get(this.type).hint; } - firmwareTitleText(firmware: FirmwareInfo): string { - return `${firmware.title} (${firmware.version})`; + packageTitleText(firpackageInfomware: OtaPackageInfo): string { + return `${firpackageInfomware.title} (${firpackageInfomware.version})`; } } diff --git a/ui-ngx/src/app/shared/components/toast.directive.ts b/ui-ngx/src/app/shared/components/toast.directive.ts index b85b115d61..452f4dc451 100644 --- a/ui-ngx/src/app/shared/components/toast.directive.ts +++ b/ui-ngx/src/app/shared/components/toast.directive.ts @@ -312,7 +312,7 @@ export class TbSnackBarComponent implements AfterViewInit, OnDestroy { ngAfterViewInit() { if (this.snackBarRef) { this.parentEl = this.data.parent.nativeElement; - this.snackBarContainerEl = this.elementRef.nativeElement.parentNode; + this.snackBarContainerEl = $(this.elementRef.nativeElement).closest('snack-bar-container')[0]; this.snackBarContainerEl.style.position = 'absolute'; this.updateContainerRect(); this.updatePosition(this.snackBarRef.containerInstance.snackBarConfig); diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index 44327e1c2c..b72e2b196d 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -121,6 +121,7 @@ export const HelpLinks = { entitiesImport: helpBaseUrl + '/docs/user-guide/bulk-provisioning', rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains', dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards', + otaUpdates: helpBaseUrl + '/docs/user-guide/ui/ota-updates', widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles', widgetsConfig: helpBaseUrl + '/docs/user-guide/ui/dashboards#widget-configuration', widgetsConfigTimeseries: helpBaseUrl + '/docs/user-guide/ui/dashboards#timeseries', diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index a782fe8256..684705590e 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -45,7 +45,6 @@ export interface WidgetLayouts { export interface GridSettings { backgroundColor?: string; - color?: string; columns?: number; margin?: number; backgroundSizeMode?: string; @@ -93,6 +92,7 @@ export interface DashboardSettings { showDashboardExport?: boolean; showUpdateDashboardImage?: boolean; toolbarAlwaysOpen?: boolean; + hideToolbar?: boolean; titleColor?: string; } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index fe7868d584..9b13c4dc72 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -27,7 +27,7 @@ import { KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; import * as _moment from 'moment'; import { AbstractControl, ValidationErrors } from '@angular/forms'; -import { FirmwareId } from '@shared/models/id/firmware-id'; +import { OtaPackageId } from '@shared/models/id/ota-package-id'; import { DashboardId } from '@shared/models/id/dashboard-id'; export enum DeviceProfileType { @@ -500,8 +500,8 @@ export interface DeviceProfile extends BaseData { defaultRuleChainId?: RuleChainId; defaultDashboardId?: DashboardId; defaultQueueName?: string; - firmwareId?: FirmwareId; - softwareId?: FirmwareId; + firmwareId?: OtaPackageId; + softwareId?: OtaPackageId; profileData: DeviceProfileData; } @@ -563,8 +563,8 @@ export interface Device extends BaseData { name: string; type: string; label: string; - firmwareId?: FirmwareId; - softwareId?: FirmwareId; + firmwareId?: OtaPackageId; + softwareId?: OtaPackageId; deviceProfileId?: DeviceProfileId; deviceData?: DeviceData; additionalInfo?: any; diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index ae2382e0ea..d088cbc88a 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -35,7 +35,7 @@ export enum EntityType { WIDGET_TYPE = 'WIDGET_TYPE', API_USAGE_STATE = 'API_USAGE_STATE', TB_RESOURCE = 'TB_RESOURCE', - FIRMWARE = 'FIRMWARE' + OTA_PACKAGE = 'OTA_PACKAGE' } export enum AliasEntityType { @@ -296,14 +296,14 @@ export const entityTypeTranslations = new Map( +export const OtaUpdateTypeTranslationMap = new Map( [ - [FirmwareType.FIRMWARE, 'firmware.types.firmware'], - [FirmwareType.SOFTWARE, 'firmware.types.software'] + [OtaUpdateType.FIRMWARE, 'ota-update.types.firmware'], + [OtaUpdateType.SOFTWARE, 'ota-update.types.software'] ] ); -export interface FirmwareInfo extends BaseData { +export interface OtaUpdateTranslation { + label: string; + required: string; + noFound: string; + noMatching: string; + hint: string; +} + +export const OtaUpdateTranslation = new Map( + [ + [OtaUpdateType.FIRMWARE, { + label: 'ota-update.assign-firmware', + required: 'ota-update.assign-firmware-required', + noFound: 'ota-update.no-firmware-text', + noMatching: 'ota-update.no-firmware-matching', + hint: 'ota-update.chose-firmware-distributed-device' + }], + [OtaUpdateType.SOFTWARE, { + label: 'ota-update.assign-software', + required: 'ota-update.assign-software-required', + noFound: 'ota-update.no-software-text', + noMatching: 'ota-update.no-software-matching', + hint: 'ota-update.chose-software-distributed-device' + }] + ] +); + +export interface OtaPagesIds { + firmwareId?: OtaPackageId; + softwareId?: OtaPackageId; +} + +export interface OtaPackageInfo extends BaseData { tenantId?: TenantId; - type: FirmwareType; + type: OtaUpdateType; deviceProfileId?: DeviceProfileId; title?: string; version?: string; hasData?: boolean; + url?: string; fileName: string; checksum?: string; checksumAlgorithm?: ChecksumAlgorithm; @@ -68,7 +101,7 @@ export interface FirmwareInfo extends BaseData { additionalInfo?: any; } -export interface Firmware extends FirmwareInfo { +export interface OtaPackage extends OtaPackageInfo { file?: File; data: string; } diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index e59832fc85..4ce7acb5c5 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -18,6 +18,7 @@ import { ContactBased } from '@shared/models/contact-based.model'; import { TenantId } from './id/tenant-id'; import { TenantProfileId } from '@shared/models/id/tenant-profile-id'; import { BaseData } from '@shared/models/base-data'; +import {Validators} from "@angular/forms"; export enum TenantProfileType { DEFAULT = 'DEFAULT' @@ -30,6 +31,8 @@ export interface DefaultTenantProfileConfiguration { maxUsers: number; maxDashboards: number; maxRuleChains: number; + maxResourcesInBytes: number; + maxOtaPackagesInBytes: number; transportTenantMsgRateLimit?: string; transportTenantTelemetryMsgRateLimit?: string; @@ -68,6 +71,8 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan maxUsers: 0, maxDashboards: 0, maxRuleChains: 0, + maxResourcesInBytes: 0, + maxOtaPackagesInBytes: 0, maxTransportMessages: 0, maxTransportDataPoints: 0, maxREExecutions: 0, diff --git a/ui-ngx/src/app/shared/models/window-message.model.ts b/ui-ngx/src/app/shared/models/window-message.model.ts index 88c8829df1..2073197e27 100644 --- a/ui-ngx/src/app/shared/models/window-message.model.ts +++ b/ui-ngx/src/app/shared/models/window-message.model.ts @@ -14,9 +14,21 @@ /// limitations under the License. /// -export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated'; +export type WindowMessageType = 'widgetException' | 'widgetEditModeInited' | 'widgetEditUpdated' | 'openDashboardMessage' | 'reloadUserMessage' | 'toggleDashboardLayout'; export interface WindowMessage { type: WindowMessageType; data?: any; } + +export interface OpenDashboardMessage { + dashboardId: string; + state?: string; + hideToolbar?: boolean; + embedded?: boolean; +} + +export interface ReloadUserMessage { + accessToken: string; + refreshToken: string; +} diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 925b297e77..f2bcdc97a4 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -141,8 +141,9 @@ import { FileSizePipe } from '@shared/pipe/file-size.pipe'; import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component'; import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-interval.component'; -import { FirmwareAutocompleteComponent } from '@shared/components/firmware/firmware-autocomplete.component'; +import { OtaPackageAutocompleteComponent } from '@shared/components/ota-package/ota-package-autocomplete.component'; import { MAT_DATE_LOCALE } from '@angular/material/core'; +import { CopyButtonComponent } from '@shared/components/button/copy-button.component'; @NgModule({ providers: [ @@ -239,8 +240,9 @@ import { MAT_DATE_LOCALE } from '@angular/material/core'; HistorySelectorComponent, EntityGatewaySelectComponent, ContactComponent, - FirmwareAutocompleteComponent, - WidgetsBundleSearchComponent + OtaPackageAutocompleteComponent, + WidgetsBundleSearchComponent, + CopyButtonComponent ], imports: [ CommonModule, @@ -411,8 +413,9 @@ import { MAT_DATE_LOCALE } from '@angular/material/core'; HistorySelectorComponent, EntityGatewaySelectComponent, ContactComponent, - FirmwareAutocompleteComponent, - WidgetsBundleSearchComponent + OtaPackageAutocompleteComponent, + WidgetsBundleSearchComponent, + CopyButtonComponent ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json index 92941c9404..128e8d6d93 100644 --- a/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json +++ b/ui-ngx/src/assets/locale/locale.constant-cs_CZ.json @@ -923,7 +923,7 @@ "existing-device-profile": "Vybrat existující profil zařízení", "specific-configuration": "Specifická konfigurace", "customer-to-assign-device": "Přiřadit zařízení zákazníkovi", - "add-credential": "Přidat přístupový údaj" + "add-credentials": "Přidat přístupový údaj" } }, "device-profile": { 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 c04da7b2e7..90a93c09e6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -219,7 +219,16 @@ "domain-schema-http": "HTTP", "domain-schema-https": "HTTPS", "domain-schema-mixed": "HTTP+HTTPS", - "enable": "Enable OAuth2 settings" + "enable": "Enable OAuth2 settings", + "domains": "Domains", + "mobile-apps": "Mobile applications", + "no-mobile-apps": "No applications configured", + "mobile-package": "Application package", + "mobile-package-unique": "Application package must be unique.", + "mobile-callback-url-scheme": "Callback URL scheme", + "add-mobile-app": "Add application", + "delete-mobile-app": "Delete application info", + "providers": "Providers" } }, "alarm": { @@ -575,7 +584,8 @@ "enter-password": "Enter password", "enter-search": "Enter search", "created-time": "Created time", - "loading": "Loading..." + "loading": "Loading...", + "proceed": "Proceed" }, "content-type": { "json": "Json", @@ -721,6 +731,7 @@ "maximum-upload-file-size": "Maximum upload file size: {{ size }}", "cannot-upload-file": "Cannot upload file", "settings": "Settings", + "layout-settings": "Layout settings", "columns-count": "Columns count", "columns-count-required": "Columns count is required.", "min-columns-count-message": "Only 10 minimum column count is allowed.", @@ -743,15 +754,19 @@ "mobile-row-height-required": "Mobile row height value is required.", "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.", "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.", + "title-settings": "Title settings", "display-title": "Display dashboard title", - "toolbar-always-open": "Keep toolbar opened", "title-color": "Title color", + "toolbar-settings": "Toolbar settings", + "hide-toolbar": "Hide toolbar", + "toolbar-always-open": "Keep toolbar opened", "display-dashboards-selection": "Display dashboards selection", "display-entities-selection": "Display entities selection", "display-filters": "Display filters", "display-dashboard-timewindow": "Display timewindow", "display-dashboard-export": "Display export", "display-update-dashboard-image": "Display update dashboard image", + "dashboard-logo-settings": "Dashboard logo settings", "display-dashboard-logo": "Display logo in dashboard fullscreen mode", "dashboard-logo-image": "Dashboard logo image", "import": "Import dashboard", @@ -1000,7 +1015,7 @@ "existing-device-profile": "Select existing device profile", "specific-configuration": "Specific configuration", "customer-to-assign-device": "Customer to assign the device", - "add-credential": "Add credential" + "add-credentials": "Add credentials" }, "unassign-devices-from-edge-title": "Are you sure you want to unassign { count, plural, 1 {1 device} other {# devices} }?", "unassign-devices-from-edge-text": "After the confirmation all selected devices will be unassigned and won't be accessible by the edge." @@ -1186,9 +1201,6 @@ "device-profile-file": "Device profile file", "invalid-device-profile-file-error": "Unable to import device profile: Invalid device profile data structure.", "lwm2m": { - "client-only-observe-after-connect-label": "Strategy", - "client-only-observe-after-connect": "{ count, plural, 1 {1: Only Observe Request to the client after the initial connection} other {2: Read All Resources & Observe Request to the client after registration} }", - "client-only-observe-after-connect-tip": "{ count, plural, 1 {Strategy 1: After the initial connection of the LWM2M Client, the server sends Observe resources Request to the client, those resources that are marked as observation in the Device profile and which exist on the LWM2M client.} other {Strategy 2: After the registration, request the client to read all the resource values for all objects that the LWM2M client has,\n then execute: the server sends Observe resources Request to the client, those resources that are marked as observation in the Device profile and which exist on the LWM2M client.} }", "object-list": "Object list", "object-list-empty": "No objects selected.", "no-objects-matching": "No objects matching '{{object}}' were found.", @@ -1236,6 +1248,7 @@ "default-min-period": "Minimum Period between two notifications (sec)", "notif-if-disabled": "Notification Storing When Disabled or Offline", "binding": "Binding", + "bootstrap-tab": "Bootstrap", "bootstrap-server": "Bootstrap Server", "lwm2m-server": "LwM2M Server", "server-host": "Host", @@ -1248,6 +1261,19 @@ "client-hold-off-time-tip": "Client Hold Off Time for use with a Bootstrap-Server only", "bootstrap-server-account-timeout": "Account after the timeout", "bootstrap-server-account-timeout-tip": "Bootstrap-Server Account after the timeout value given by this resource.", + "others-tab": "Other settings...", + "client-strategy": "Client strategy when connecting", + "client-strategy-label": "Strategy", + "client-strategy-connect": "{ count, plural, 1 {1: Only Observe Request to the client after the initial connection} other {2: Read All Resources & Observe Request to the client after registration} }", + "client-strategy-tip": "{ count, plural, 1 {Strategy 1: After the initial connection of the LWM2M Client, the server sends Observe resources Request to the client, those resources that are marked as observation in the Device profile and which exist on the LWM2M client.} other {Strategy 2: After the registration, request the client to read all the resource values for all objects that the LWM2M client has,\n then execute: the server sends Observe resources Request to the client, those resources that are marked as observation in the Device profile and which exist on the LWM2M client.} }", + "ota-update-strategy": "Ota update strategy", + "fw-update-strategy-label": "Firmware update strategy", + "fw-update-strategy": "{ count, plural, 1 {Push firmware update as binary file using Object 5 and Resource 0 (Package).} 2 {Auto-generate unique CoAP URL to download the package and push firmware update as Object 5 and Resource 1 (Package URI).} other {Push firmware update as binary file using Object 19 and Resource 0 (Data).} }", + "sw-update-strategy-label": "Software update strategy", + "sw-update-strategy": "{ count, plural, 1 {Push binary file using Object 9 and Resource 2 (Package).} other {Auto-generate unique CoAP URL to download the package and push software update using Object 9 and Resource 3 (Package URI).} }", + "ota-update-recourse": "Ota update Coap recourse", + "fw-update-recourse": "Firmware update Coap recourse", + "sw-update-recourse": "Software update Coap recourse", "config-json-tab": "Json Config Profile Device" } }, @@ -1516,7 +1542,7 @@ "list-of-edges": "{ count, plural, 1 {One edge} other {List of # edges} }", "edge-name-starts-with": "Edges whose names start with '{{prefix}}'", "type-tb-resource": "Resource", - "type-firmware": "Firmware" + "type-ota-package": "OTA package" }, "entity-field": { "created-time": "Created time", @@ -1927,51 +1953,6 @@ "inherit-owner": "Inherit from owner", "source-attribute-not-set": "If source attribute isn't set" }, - "firmware": { - "add": "Add firmware", - "checksum": "Checksum", - "checksum-copied-message": "Firmware checksum has been copied to clipboard", - "checksum-required": "Checksum is required.", - "checksum-algorithm": "Checksum algorithm", - "content-type": "Content type", - "copy-checksum": "Copy checksum", - "copyId": "Copy firmware Id", - "description": "Description", - "delete": "Delete firmware", - "delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.", - "delete-firmware-title": "Are you sure you want to delete the firmware '{{firmwareTitle}}'?", - "delete-firmwares-action-title": "Delete { count, plural, 1 {1 firmware} other {# firmwares} }", - "delete-firmwares-text": "Be careful, after the confirmation all selected resources will be removed.", - "delete-firmwares-title": "Are you sure you want to delete { count, plural, 1 {1 firmware} other {# firmwares} }?", - "download": "Download firmware", - "drop-file": "Drop a firmware file or click to select a file to upload.", - "empty": "Firmware is empty", - "idCopiedMessage": "Firmware Id has been copied to clipboard", - "no-firmware-matching": "No firmware matching '{{entity}}' were found.", - "no-firmware-text": "No firmwares found", - "no-software-matching": "No sowtware matching '{{entity}}' were found.", - "no-software-text": "No software found", - "file-name": "File name", - "file-size": "File size", - "file-size-bytes": "File size in bytes", - "firmware": "Firmware", - "firmware-details": "Firmware details", - "firmware-required": "Firmware is required.", - "search": "Search firmwares", - "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected", - "software": "Software", - "software-required": "Software is required.", - "title": "Title", - "title-required": "Title is required.", - "type": "Firmware type", - "types": { - "firmware": "Firmware", - "software": "Software" - }, - "version": "Version", - "version-required": "Version is required.", - "warning-after-save-no-edit": "Once the firmware is saved, it will not be possible to change the title and version fields." - }, "fullscreen": { "expand": "Expand to fullscreen", "exit": "Exit fullscreen", @@ -2191,6 +2172,64 @@ "or": "or", "error": "Login error" }, + "ota-update": { + "add": "Add package", + "assign-firmware": "Assigned firmware", + "assign-firmware-required": "Assigned firmware is required", + "assign-software": "Assigned software", + "assign-software-required": "Assigned software is required", + "auto-generate-checksum": "Auto-generate checksum", + "checksum": "Checksum", + "checksum-hint": "If checksum is empty, it will be generated automatically", + "checksum-algorithm": "Checksum algorithm", + "checksum-copied-message": "Package checksum has been copied to clipboard", + "change-firmware": "Change of the firmware may cause update of { count, plural, 1 {1 device} other {# devices} }.", + "change-software": "Change of the software may cause update of { count, plural, 1 {1 device} other {# devices} }.", + "chose-compatible-device-profile": "The uploaded package will be available only for devices with the chosen profile.", + "chose-firmware-distributed-device": "Choose firmware that will be distributed to the devices", + "chose-software-distributed-device": "Choose software that will be distributed to the devices", + "content-type": "Content type", + "copy-checksum": "Copy checksum", + "copy-direct-url": "Copy direct URL", + "copyId": "Copy package Id", + "copied": "Copied!", + "delete": "Delete package", + "delete-ota-update-text": "Be careful, after the confirmation the OTA update will become unrecoverable.", + "delete-ota-update-title": "Are you sure you want to delete the OTA update '{{title}}'?", + "delete-ota-updates-text": "Be careful, after the confirmation all selected OTA updates will be removed.", + "delete-ota-updates-title": "Are you sure you want to delete { count, plural, 1 {1 OTA update} other {# OTA updates} }?", + "description": "Description", + "direct-url": "Direct URL", + "direct-url-copied-message": "Package direct URL has been copied to clipboard", + "direct-url-required": "Direct URL is required", + "download": "Download package", + "drop-file": "Drop a package file or click to select a file to upload.", + "file-name": "File name", + "file-size": "File size", + "file-size-bytes": "File size in bytes", + "idCopiedMessage": "Package Id has been copied to clipboard", + "no-firmware-matching": "No compatible Firmware OTA Update packages matching '{{entity}}' were found.", + "no-firmware-text": "No compatible Firmware OTA Update packages provisioned.", + "no-packages-text": "No packages found", + "no-software-matching": "No compatible Software OTA Update packages matching '{{entity}}' were found.", + "no-software-text": "No compatible Software OTA Update packages provisioned.", + "ota-update": "OTA update", + "ota-update-details": "OTA update details", + "ota-updates": "OTA updates", + "package-type": "Package type", + "packages-repository": "Packages repository", + "search": "Search packages", + "selected-package": "{ count, plural, 1 {1 package} other {# packages} } selected", + "title": "Title", + "title-required": "Title is required.", + "types": { + "firmware": "Firmware", + "software": "Software" + }, + "version": "Version", + "version-required": "Version is required.", + "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." + }, "position": { "top": "Top", "bottom": "Bottom", @@ -2493,6 +2532,12 @@ "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)", "maximum-rule-chains-required": "Maximum number of rule chains is required.", "maximum-rule-chains-range": "Maximum number of rule chains can't be negative", + "maximum-resources-sum-data-size": "Maximum sum of resource files size in bytes (0 - unlimited)", + "maximum-resources-sum-data-size-required": "Maximum sum of resource files size is required.", + "maximum-resources-sum-data-size-range": "Maximum sum of resource files size can`t be negative", + "maximum-ota-packages-sum-data-size": "Maximum sum of ota package files size in bytes (0 - unlimited)", + "maximum-ota-package-sum-data-size-required": "Maximum sum of ota package files size is required.", + "maximum-ota-package-sum-data-size-range": "Maximum sum of ota package files size can`t be negative", "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.", "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.", "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.", @@ -2517,6 +2562,9 @@ "default-storage-ttl-days": "Default storage TTL days (0 - unlimited)", "default-storage-ttl-days-required": "Default storage TTL days is required.", "default-storage-ttl-days-range": "Default storage TTL days can't be negative", + "alarms-ttl-days": "Alarms TTL days (0 - unlimited)", + "alarms-ttl-days-required": "Alarms TTL days required", + "alarms-ttl-days-days-range": "Alarms TTL days can't be negative", "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", "max-rule-node-executions-per-message-range": "Maximum number of rule node executions per message can't be negative", diff --git a/ui-ngx/src/assets/locale/locale.constant-es_ES.json b/ui-ngx/src/assets/locale/locale.constant-es_ES.json index 9d18102a43..fc1049ca91 100644 --- a/ui-ngx/src/assets/locale/locale.constant-es_ES.json +++ b/ui-ngx/src/assets/locale/locale.constant-es_ES.json @@ -947,7 +947,7 @@ "existing-device-profile": "Seleccionar un perfil existente", "specific-configuration": "Configuración específica", "customer-to-assign-device": "Cliente al que asignar el dispositivo", - "add-credential": "Añadir credencial" + "add-credentials": "Añadir credencial" }, "assign-device-to-edge-text": "Seleccione los dispositivos para asignar al borde", "unassign-device-from-edge-title": "¿Está seguro de que desea desasignar el dispositivo '{{deviceName}}'?", diff --git a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json index cbe962bc2a..f966203fbf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-ko_KR.json +++ b/ui-ngx/src/assets/locale/locale.constant-ko_KR.json @@ -920,7 +920,7 @@ "existing-device-profile": "기존 장치 프로파일 선택", "specific-configuration": "특수 설정", "customer-to-assign-device": "장치에 할당할 커스터머", - "add-credential": "크리덴셜 추가" + "add-credentials": "크리덴셜 추가" } }, "device-profile": { diff --git a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json index 7dd60aaa2f..e57ee78e87 100644 --- a/ui-ngx/src/assets/locale/locale.constant-sl_SI.json +++ b/ui-ngx/src/assets/locale/locale.constant-sl_SI.json @@ -920,7 +920,7 @@ "existing-device-profile": "Select existing device profile", "specific-configuration": "Specific configuration", "customer-to-assign-device": "Customer to assign the device", - "add-credential": "Add credential" + "add-credentials": "Add credentials" } }, "device-profile": { 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 b7a3078c3b..99b0864d36 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -1079,7 +1079,7 @@ "view-credentials": "查看凭据", "view-devices": "查看设备", "wizard": { - "add-credential": "添加凭据", + "add-credentials": "添加凭据", "customer-to-assign-device": "客户分配设备", "device-details": "设备详细信息", "device-wizard": "设备向导", diff --git a/ui-ngx/src/theme.scss b/ui-ngx/src/theme.scss index 383c992d6c..25e1131e63 100644 --- a/ui-ngx/src/theme.scss +++ b/ui-ngx/src/theme.scss @@ -174,6 +174,48 @@ $tb-dark-theme: get-tb-dark-theme( } } +@mixin _mat-toolbar-inverse-color($palette) { + background: mat-color($palette, default-contrast); + color: $dark-primary-text; +} + +@mixin mat-fab-toolbar-inverse-theme($theme) { + $primary: map-get($theme, primary); + $accent: map-get($theme, accent); + $warn: map-get($theme, warn); + $background: map-get($theme, foreground); + $foreground: map-get($theme, background); + + mat-fab-toolbar { + .mat-fab-toolbar-background { + background: mat-color($background, app-bar); + color: mat-color($foreground, text); + } + &.mat-primary { + .mat-fab-toolbar-background { + @include _mat-toolbar-inverse-color($primary); + } + } + mat-toolbar { + &.mat-primary { + @include _mat-toolbar-inverse-color($primary); + button.mat-icon-button { + mat-icon { + color: mat-color($primary); + } + } + } + } + .mat-fab { + &.mat-primary { + background: mat-color($primary, default-contrast); + color: mat-color($primary); + } + } + } + +} + @mixin tb-components-theme($theme) { $primary: map-get($theme, primary); @@ -184,6 +226,10 @@ $tb-dark-theme: get-tb-dark-theme( } @include mat-fab-toolbar-theme($tb-theme); + + div.tb-dashboard-page.mobile-app { + @include mat-fab-toolbar-inverse-theme($tb-theme); + } } .tb-default { @@ -195,3 +241,4 @@ $tb-dark-theme: get-tb-dark-theme( .tb-dark { @include angular-material-theme($tb-dark-theme); } +