diff --git a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
index 5fdced278b..4e5115bb98 100644
--- a/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
+++ b/application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
@@ -21,7 +21,7 @@
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n hasDataPageLink: true,\n warnOnPageDataOverflow: false,\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n },\n 'rowDoubleClick': {\n name: 'widget-action.row-double-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesTableSettings\",\n \"properties\": {\n \"entitiesTitle\": {\n \"title\": \"Entities table title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"enableSearch\": {\n \"title\": \"Enable entities search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableSelectColumnDisplay\": {\n \"title\": \"Enable select columns to display\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"displayEntityName\": {\n \"title\": \"Display entity name column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"entityNameColumnTitle\": {\n \"title\": \"Entity name column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityLabel\": {\n \"title\": \"Display entity label column\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"entityLabelColumnTitle\": {\n \"title\": \"Entity label column title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"displayEntityType\": {\n \"title\": \"Display entity type column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"defaultSortOrder\": {\n \"title\": \"Default sort order\",\n \"type\": \"string\",\n \"default\": \"entityName\"\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"entitiesTitle\",\n \"enableSearch\",\n \"enableSelectColumnDisplay\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"displayEntityName\",\n \"entityNameColumnTitle\",\n \"displayEntityLabel\",\n \"entityLabelColumnTitle\",\n \"displayEntityType\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"defaultSortOrder\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"defaultColumnVisibility\": {\n \"title\": \"Default column visibility\",\n \"type\": \"string\",\n \"default\": \"visible\"\n },\n \"columnSelectionToDisplay\": {\n \"title\": \"Column selection in 'Columns to Display'\",\n \"type\": \"string\",\n \"default\": \"enabled\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/entity/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n },\n {\n \"key\": \"defaultColumnVisibility\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"visible\",\n \"label\": \"Visible\"\n },\n {\n \"value\": \"hidden\",\n \"label\": \"Hidden\"\n } \n ]\n },\n {\n \"key\": \"columnSelectionToDisplay\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"enabled\",\n \"label\": \"Enabled\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n } \n ]\n }\n ]\n}",
- "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"
\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"type\":\"customPretty\",\"customHtml\":\"\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
+ "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"enableSearch\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true,\"entitiesTitle\":\"Device admin table\",\"enableSelectColumnDisplay\":true,\"enableStickyHeader\":true,\"enableStickyAction\":true,\"reserveSpaceForHiddenAction\":\"true\",\"displayEntityLabel\":false,\"useRowStyleFunction\":false},\"title\":\"Device admin table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"widgetStyle\":{},\"displayTimewindow\":true,\"actions\":{\"headerButton\":[{\"name\":\"Add device\",\"icon\":\"add\",\"type\":\"customPretty\",\"customHtml\":\"\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenAddDeviceDialog();\\n\\nfunction openAddDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, AddDeviceDialogController).subscribe();\\n}\\n\\nfunction AddDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.addDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.addDeviceFormGroup.markAsPristine();\\n let device = {\\n name: vm.addDeviceFormGroup.get('deviceName').value,\\n type: vm.addDeviceFormGroup.get('deviceType').value,\\n label: vm.addDeviceFormGroup.get('deviceLabel').value\\n };\\n deviceService.saveDevice(device).subscribe(\\n function (device) {\\n saveAttributes(device.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n function saveAttributes(entityId) {\\n let attributes = vm.addDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, \\\"SERVER_SCOPE\\\", attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"id\":\"70837a9d-c3de-a9a7-03c5-dccd14998758\"}],\"actionCellButton\":[{\"name\":\"Edit device\",\"icon\":\"edit\",\"useShowWidgetActionFunction\":null,\"showWidgetActionFunction\":\"return true;\",\"type\":\"customPretty\",\"customHtml\":\"\\n\",\"customCss\":\"\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\nlet attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));\\n\\nopenEditDeviceDialog();\\n\\nfunction openEditDeviceDialog() {\\n customDialog.customDialog(htmlTemplate, EditDeviceDialogController).subscribe();\\n}\\n\\nfunction EditDeviceDialogController(instance) {\\n let vm = instance;\\n \\n vm.device = null;\\n vm.attributes = {};\\n \\n vm.editDeviceFormGroup = vm.fb.group({\\n deviceName: ['', [vm.validators.required]],\\n deviceType: ['', [vm.validators.required]],\\n deviceLabel: [''],\\n attributes: vm.fb.group({\\n latitude: [null],\\n longitude: [null]\\n }) \\n });\\n \\n vm.cancel = function() {\\n vm.dialogRef.close(null);\\n };\\n \\n vm.save = function() {\\n vm.editDeviceFormGroup.markAsPristine();\\n if (vm.editDeviceFormGroup.get('deviceType').value !== vm.device.type) {\\n delete vm.device.deviceProfileId;\\n }\\n vm.device.name = vm.editDeviceFormGroup.get('deviceName').value,\\n vm.device.type = vm.editDeviceFormGroup.get('deviceType').value,\\n vm.device.label = vm.editDeviceFormGroup.get('deviceLabel').value\\n deviceService.saveDevice(vm.device).subscribe(\\n function () {\\n saveAttributes().subscribe(\\n function () {\\n widgetContext.updateAliases();\\n vm.dialogRef.close(null);\\n }\\n );\\n }\\n );\\n };\\n \\n getEntityInfo();\\n \\n function getEntityInfo() {\\n deviceService.getDevice(entityId.id).subscribe(\\n function (device) {\\n attributeService.getEntityAttributes(entityId, 'SERVER_SCOPE',\\n ['latitude', 'longitude']).subscribe(\\n function (attributes) {\\n for (let i = 0; i < attributes.length; i++) {\\n vm.attributes[attributes[i].key] = attributes[i].value; \\n }\\n vm.device = device;\\n vm.editDeviceFormGroup.patchValue(\\n {\\n deviceName: vm.device.name,\\n deviceType: vm.device.type,\\n deviceLabel: vm.device.label,\\n attributes: {\\n latitude: vm.attributes.latitude,\\n longitude: vm.attributes.longitude\\n }\\n }, {emitEvent: false}\\n );\\n } \\n );\\n }\\n ); \\n }\\n \\n function saveAttributes() {\\n let attributes = vm.editDeviceFormGroup.get('attributes').value;\\n let attributesArray = [];\\n for (let key in attributes) {\\n attributesArray.push({key: key, value: attributes[key]});\\n }\\n if (attributesArray.length > 0) {\\n return attributeService.saveEntityAttributes(entityId, 'SERVER_SCOPE', attributesArray);\\n } else {\\n return widgetContext.rxjs.of([]);\\n }\\n }\\n}\",\"customResources\":[],\"openInSeparateDialog\":false,\"openInPopover\":false,\"id\":\"93931e52-5d7c-903e-67aa-b9435df44ff4\"},{\"name\":\"Delete device\",\"icon\":\"delete\",\"type\":\"custom\",\"customFunction\":\"let $injector = widgetContext.$scope.$injector;\\nlet dialogs = $injector.get(widgetContext.servicesMap.get('dialogs'));\\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\\n\\nopenDeleteDeviceDialog();\\n\\nfunction openDeleteDeviceDialog() {\\n let title = \\\"Are you sure you want to delete the device \\\" + entityName + \\\"?\\\";\\n let content = \\\"Be careful, after the confirmation, the device and all related data will become unrecoverable!\\\";\\n dialogs.confirm(title, content, 'Cancel', 'Delete').subscribe(\\n function (result) {\\n if (result) {\\n deleteDevice();\\n }\\n }\\n );\\n}\\n\\nfunction deleteDevice() {\\n deviceService.deleteDevice(entityId.id).subscribe(\\n function () {\\n widgetContext.updateAliases();\\n }\\n );\\n}\\n\",\"id\":\"ec2708f6-9ff0-186b-e4fc-7635ebfa3074\"}]}}"
}
},
{
diff --git a/application/src/main/data/upgrade/3.3.3/schema_update.sql b/application/src/main/data/upgrade/3.3.3/schema_update.sql
new file mode 100644
index 0000000000..eed1cab1ea
--- /dev/null
+++ b/application/src/main/data/upgrade/3.3.3/schema_update.sql
@@ -0,0 +1,29 @@
+--
+-- Copyright © 2016-2022 The Thingsboard Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+
+DELETE from ota_package as op WHERE NOT EXISTS(SELECT * FROM device_profile dp where op.device_profile_id = dp.id);
+
+DO
+$$
+ BEGIN
+ IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_device_profile_ota_package') THEN
+ ALTER TABLE ota_package
+ ADD CONSTRAINT fk_device_profile_ota_package
+ FOREIGN KEY (device_profile_id) REFERENCES device_profile (id)
+ ON DELETE CASCADE;
+ END IF;
+ END;
+$$;
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index 9384bbf7b4..f5597f639e 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -26,10 +26,7 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator;
-import org.thingsboard.server.actors.device.SessionTimeoutCheckMsg;
-import org.thingsboard.server.actors.ruleChain.RuleChainInputMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
-import org.thingsboard.server.actors.ruleChain.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState;
@@ -65,7 +62,7 @@ import java.util.Optional;
@Slf4j
public class TenantActor extends RuleChainManagerActor {
- private boolean isRuleEngineForCurrentTenant;
+ private boolean isRuleEngine;
private boolean isCore;
private ApiUsageState apiUsageState;
@@ -78,39 +75,37 @@ public class TenantActor extends RuleChainManagerActor {
@Override
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
- log.info("[{}] Starting tenant actor.", tenantId);
+ log.debug("[{}] Starting tenant actor.", tenantId);
try {
Tenant tenant = systemContext.getTenantService().findTenantById(tenantId);
if (tenant == null) {
cantFindTenant = true;
log.info("[{}] Started tenant actor for missing tenant.", tenantId);
} else {
- apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenant.getId()));
-
// This Service may be started for specific tenant only.
Optional isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId());
isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
- isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
- if (isRuleEngineForCurrentTenant) {
+ isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
+ if (isRuleEngine) {
try {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {
- if (apiUsageState.isReExecEnabled()) {
- log.info("[{}] Going to init rule chains", tenantId);
+ if (getApiUsageState().isReExecEnabled()) {
+ log.debug("[{}] Going to init rule chains", tenantId);
initRuleChains();
} else {
log.info("[{}] Skip init of the rule chains due to API limits", tenantId);
}
} else {
- isRuleEngineForCurrentTenant = false;
+ isRuleEngine = false;
}
} catch (Exception e) {
cantFindTenant = true;
}
}
- log.info("[{}] Tenant actor started.", tenantId);
+ log.debug("[{}] Tenant actor started.", tenantId);
}
} catch (Exception e) {
log.warn("[{}] Unknown failure", tenantId, e);
@@ -193,12 +188,12 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
- if (!isRuleEngineForCurrentTenant) {
+ if (!isRuleEngine) {
log.warn("RECEIVED INVALID MESSAGE: {}", msg);
return;
}
TbMsg tbMsg = msg.getMsg();
- if (apiUsageState.isReExecEnabled()) {
+ if (getApiUsageState().isReExecEnabled()) {
if (tbMsg.getRuleChainId() == null) {
if (getRootChainActor() != null) {
getRootChainActor().tell(msg);
@@ -222,7 +217,7 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onRuleChainMsg(RuleChainAwareMsg msg) {
- if (apiUsageState.isReExecEnabled()) {
+ if (getApiUsageState().isReExecEnabled()) {
getOrCreateActor(msg.getRuleChainId()).tell(msg);
}
}
@@ -241,7 +236,7 @@ public class TenantActor extends RuleChainManagerActor {
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) {
- ApiUsageState old = apiUsageState;
+ ApiUsageState old = getApiUsageState();
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));
if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) {
log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId);
@@ -261,7 +256,7 @@ public class TenantActor extends RuleChainManagerActor {
edgeRpcService.updateEdge(tenantId, edge);
}
}
- } else if (isRuleEngineForCurrentTenant) {
+ } else if (isRuleEngine) {
TbActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
@@ -289,6 +284,13 @@ public class TenantActor extends RuleChainManagerActor {
systemContext.getEdgeRpcService().onEdgeEvent(tenantId, msg.getEdgeId());
}
+ private ApiUsageState getApiUsageState() {
+ if (apiUsageState == null) {
+ apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));
+ }
+ return apiUsageState;
+ }
+
public static class ActorCreator extends ContextBasedCreator {
private final TenantId tenantId;
diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
index 5f79efaffa..f3d73f13e5 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java
@@ -169,7 +169,7 @@ public class EdgeController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
edge.getId(), edge);
- Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true));
+ Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser());
return savedEdge;
diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
index 3f3d1ad75c..7ccfd6fe4d 100644
--- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
@@ -236,17 +236,12 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
return getOrFetchState(tenantId, tenantId).getApiUsageState();
} else {
- updateLock.lock();
- try {
- state = otherUsageStates.get(tenantId);
- if (state == null) {
- state = apiUsageStateService.findTenantApiUsageState(tenantId);
- if (state != null) {
- otherUsageStates.put(tenantId, state);
- }
+ state = otherUsageStates.get(tenantId);
+ if (state == null) {
+ state = apiUsageStateService.findTenantApiUsageState(tenantId);
+ if (state != null) {
+ otherUsageStates.put(tenantId, state);
}
- } finally {
- updateLock.unlock();
}
return state;
}
diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
index d24bfed212..cd655a2467 100644
--- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
@@ -34,11 +34,13 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
+import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
-import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
+import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration;
+import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@@ -52,6 +54,7 @@ import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Collection;
+import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Objects;
@@ -225,11 +228,14 @@ public class DeviceBulkImportService extends AbstractBulkImportService {
deviceProfile.setTransportType(DeviceTransportType.LWM2M);
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
+ Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
+ transportConfiguration.setBootstrap(Collections.emptyList());
+ transportConfiguration.setClientLwM2mSettings(new OtherConfiguration(1,1,1, PowerMode.DRX, null, null, null, null, null));
+ transportConfiguration.setObserveAttr(new TelemetryMappingConfiguration(Collections.emptyMap(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptyMap()));
+
DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
- DeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
-
deviceProfileData.setConfiguration(configuration);
deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfileData.setProvisionConfiguration(provisionConfiguration);
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
index 8309fa7ef6..55779f19ef 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
@@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
+import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@@ -38,7 +39,6 @@ import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
-import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@@ -93,7 +93,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException {
edge.setRootRuleChainId(ruleChainId);
- Edge savedEdge = edgeService.saveEdge(edge, true);
+ Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null);
return savedEdge;
}
@@ -122,7 +122,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
- log.trace("Pushing notification to edge {}", edgeNotificationMsg);
+ log.debug("Pushing notification to edge {}", edgeNotificationMsg);
try {
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
@@ -153,11 +153,12 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
break;
default:
- log.debug("Edge event type [{}] is not designed to be pushed to edge", type);
+ log.warn("Edge event type [{}] is not designed to be pushed to edge", type);
}
} catch (Exception e) {
callback.onFailure(e);
- log.error("Can't push to edge updates, edgeNotificationMsg [{}]", edgeNotificationMsg, e);
+ String errMsg = String.format("Can't push to edge updates, edgeNotificationMsg [%s]", edgeNotificationMsg);
+ log.error(errMsg, e);
} finally {
callback.onSuccess();
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
index ff393a2b3d..615a7cddd2 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
@@ -68,7 +68,7 @@ public class EdgeBulkImportService extends AbstractBulkImportService {
@Override
protected Edge saveEntity(Edge entity, Map fields) {
- return edgeService.saveEdge(entity, true);
+ return edgeService.saveEdge(entity);
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
index fdcbdbd849..e4f32f6a89 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
@@ -643,7 +643,9 @@ public final class EdgeGrpcSession implements Closeable {
}
}
} catch (Exception e) {
- log.error("[{}] Can't process uplink msg [{}]", this.sessionId, uplinkMsg, e);
+ String errMsg = String.format("[%s] Can't process uplink msg [%s]", this.sessionId, uplinkMsg);
+ log.error(errMsg, e);
+ return Futures.immediateFailedFuture(e);
}
return Futures.allAsList(result);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
index ac261fef91..10d1ae88c5 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
@@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
+import org.thingsboard.server.cluster.TbClusterService;
+import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
@@ -43,6 +45,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
+import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
@@ -62,7 +65,6 @@ import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstruc
import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
-import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService;
@Slf4j
@@ -129,6 +131,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected WidgetTypeService widgetTypeService;
+ @Autowired
+ protected DataValidator deviceValidator;
+
@Autowired
protected EntityDataMsgConstructor entityDataMsgConstructor;
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
index 20c8ac7933..f19ec25b27 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
@@ -26,7 +26,6 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
@@ -45,11 +44,14 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
+import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
+import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
@@ -59,7 +61,6 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent;
-import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
import java.util.UUID;
@@ -79,38 +80,37 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
String deviceName = deviceUpdateMsg.getName();
Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName);
if (device != null) {
- PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
- PageData pageData;
- do {
- pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink);
- boolean update = false;
- if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
- if (pageData.getData().contains(edge.getId())) {
- update = true;
- }
- if (pageData.hasNext()) {
- pageLink = pageLink.nextPageLink();
- }
+ boolean deviceAlreadyExistsForThisEdge = isDeviceAlreadyExistsOnCloudForThisEdge(tenantId, edge, device);
+ if (deviceAlreadyExistsForThisEdge) {
+ log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " +
+ "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
+ updateDevice(tenantId, edge, deviceUpdateMsg);
+ } else {
+ log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
+ "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
+ String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15);
+ Device newDevice;
+ try {
+ newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName);
+ } catch (DataValidationException e) {
+ String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg);
+ log.error(errMsg, e);
+ return Futures.immediateFuture(null);
}
-
- if (update) {
- log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " +
- "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
- updateDevice(tenantId, edge, deviceUpdateMsg);
- } else {
- log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
- "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
- String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15);
- Device newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName);
- ObjectNode body = mapper.createObjectNode();
- body.put("conflictName", deviceName);
- saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
- saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
- }
- } while (pageData != null && pageData.hasNext());
+ ObjectNode body = mapper.createObjectNode();
+ body.put("conflictName", deviceName);
+ saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
+ saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
+ }
} else {
log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg);
- device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName());
+ try {
+ device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName());
+ } catch (DataValidationException e) {
+ String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg);
+ log.error(errMsg, e);
+ return Futures.immediateFuture(null);
+ }
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null);
}
break;
@@ -131,6 +131,23 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
return Futures.immediateFuture(null);
}
+ private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) {
+ PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
+ PageData pageData;
+ do {
+ pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink);
+ if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
+ if (pageData.getData().contains(edge.getId())) {
+ return true;
+ }
+ if (pageData.hasNext()) {
+ pageLink = pageLink.nextPageLink();
+ }
+ }
+ } while (pageData != null && pageData.hasNext());
+ return false;
+ }
+
public ListenableFuture processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) {
log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg);
DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB()));
@@ -194,7 +211,6 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
if (device == null) {
device = new Device();
device.setTenantId(tenantId);
- device.setId(deviceId);
device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId()));
created = true;
}
@@ -214,6 +230,12 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
deviceUpdateMsg.getDeviceProfileIdLSB()));
device.setDeviceProfileId(deviceProfileId);
}
+ if (created) {
+ deviceValidator.validate(device, Device::getTenantId);
+ device.setId(deviceId);
+ } else {
+ deviceValidator.validate(device, Device::getTenantId);
+ }
Device savedDevice = deviceService.saveDevice(device, false);
tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false);
if (created) {
diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java
index d0b86ddcba..89ce662467 100644
--- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java
@@ -94,27 +94,20 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type,
new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
- EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
- PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
- PageData pageData;
+ EdgeId edgeId = null;
+ if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) {
+ edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
+ }
switch (actionType) {
case ADDED: // used only for USER entity
case UPDATED:
case CREDENTIALS_UPDATED:
- do {
- pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
- if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
- for (EdgeId relatedEdgeId : pageData.getData()) {
- saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
- }
- if (pageData.hasNext()) {
- pageLink = pageLink.nextPageLink();
- }
- }
- } while (pageData != null && pageData.hasNext());
+ pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
break;
case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER:
+ PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
+ PageData pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
@@ -147,7 +140,11 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
} while (pageData != null && pageData.hasNext());
break;
case DELETED:
- saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
+ if (edgeId != null) {
+ saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
+ } else {
+ pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
+ }
break;
case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE:
@@ -159,6 +156,22 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
}
}
+ private void pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) {
+ PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
+ PageData pageData;
+ do {
+ pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
+ if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
+ for (EdgeId relatedEdgeId : pageData.getData()) {
+ saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
+ }
+ if (pageData.hasNext()) {
+ pageLink = pageLink.nextPageLink();
+ }
+ }
+ } while (pageData != null && pageData.hasNext());
+ }
+
private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData pageData;
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index e70f5df850..f7b9e5cad1 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -524,6 +524,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Updating TTL cleanup procedure for the event table...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", "schema_event_ttl_procedure.sql");
loadSql(schemaUpdateFile, conn);
+ schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", SCHEMA_UPDATE_SQL);
+ loadSql(schemaUpdateFile, conn);
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;");
diff --git a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
index b4337fb9f3..d2a6369c51 100644
--- a/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
@@ -29,12 +29,16 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.async.DeferredResult;
+import org.thingsboard.common.util.KvUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
+import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
+import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
@@ -42,6 +46,10 @@ import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
+import org.thingsboard.server.common.data.query.FilterPredicateType;
+import org.thingsboard.server.common.data.query.KeyFilter;
+import org.thingsboard.server.common.data.query.KeyFilterPredicate;
+import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
@@ -52,6 +60,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
+import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import java.util.ArrayList;
import java.util.Collection;
@@ -59,7 +68,9 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -93,9 +104,81 @@ public class DefaultEntityQueryService implements EntityQueryService {
@Override
public PageData findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) {
+ if (query.getKeyFilters() != null) {
+ resolveDynamicValuesInPredicates(
+ query.getKeyFilters().stream()
+ .map(KeyFilter::getPredicate)
+ .collect(Collectors.toList()),
+ securityUser
+ );
+ }
return entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query);
}
+ private void resolveDynamicValuesInPredicates(List predicates, SecurityUser user) {
+ predicates.forEach(predicate -> {
+ if (predicate.getType() == FilterPredicateType.COMPLEX) {
+ resolveDynamicValuesInPredicates(
+ ((ComplexFilterPredicate) predicate).getPredicates(),
+ user
+ );
+ } else {
+ setResolvedValue(user, (SimpleKeyFilterPredicate>) predicate);
+ }
+ });
+ }
+
+ private void setResolvedValue(SecurityUser user, SimpleKeyFilterPredicate> predicate) {
+ DynamicValue> dynamicValue = predicate.getValue().getDynamicValue();
+ if (dynamicValue != null && dynamicValue.getResolvedValue() == null) {
+ resolveDynamicValue(dynamicValue, user, predicate.getType());
+ }
+ }
+
+ private void resolveDynamicValue(DynamicValue dynamicValue, SecurityUser user, FilterPredicateType predicateType) {
+ EntityId entityId;
+ switch (dynamicValue.getSourceType()) {
+ case CURRENT_TENANT:
+ entityId = user.getTenantId();
+ break;
+ case CURRENT_CUSTOMER:
+ entityId = user.getCustomerId();
+ break;
+ case CURRENT_USER:
+ entityId = user.getId();
+ break;
+ default:
+ throw new RuntimeException("Not supported operation for source type: {" + dynamicValue.getSourceType() + "}");
+ }
+
+ try {
+ Optional valueOpt = attributesService.find(user.getTenantId(), entityId,
+ TbAttributeSubscriptionScope.SERVER_SCOPE.name(), dynamicValue.getSourceAttribute()).get();
+
+ if (valueOpt.isPresent()) {
+ AttributeKvEntry entry = valueOpt.get();
+ Object resolved = null;
+ switch (predicateType) {
+ case STRING:
+ resolved = KvUtil.getStringValue(entry);
+ break;
+ case NUMERIC:
+ resolved = KvUtil.getDoubleValue(entry);
+ break;
+ case BOOLEAN:
+ resolved = KvUtil.getBoolValue(entry);
+ break;
+ case COMPLEX:
+ break;
+ }
+
+ dynamicValue.setResolvedValue((T) resolved);
+ }
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@Override
public PageData findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) {
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query);
diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
index 41871e3dae..23ba6fd19e 100644
--- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
+++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
@@ -31,7 +31,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
public CustomerUserPermissions() {
super();
- put(Resource.ALARM, TenantAdminPermissions.tenantEntityPermissionChecker);
+ put(Resource.ALARM, customerAlarmPermissionChecker);
put(Resource.ASSET, customerEntityPermissionChecker);
put(Resource.DEVICE, customerEntityPermissionChecker);
put(Resource.CUSTOMER, customerPermissionChecker);
@@ -44,6 +44,19 @@ public class CustomerUserPermissions extends AbstractPermissions {
put(Resource.RPC, rpcPermissionChecker);
}
+ private static final PermissionChecker customerAlarmPermissionChecker = new PermissionChecker() {
+ @Override
+ public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
+ if (!user.getTenantId().equals(entity.getTenantId())) {
+ return false;
+ }
+ if (!(entity instanceof HasCustomerId)) {
+ return false;
+ }
+ return user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId());
+ }
+ };
+
private static final PermissionChecker customerEntityPermissionChecker =
new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS,
Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES,
@@ -62,10 +75,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!(entity instanceof HasCustomerId)) {
return false;
}
- if (!operation.equals(Operation.CLAIM_DEVICES) && !user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId())) {
- return false;
- }
- return true;
+ return operation.equals(Operation.CLAIM_DEVICES) || user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId());
}
};
@@ -78,10 +88,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!super.hasPermission(user, operation, entityId, entity)) {
return false;
}
- if (!user.getCustomerId().equals(entityId)) {
- return false;
- }
- return true;
+ return user.getCustomerId().equals(entityId);
}
};
@@ -98,10 +105,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!user.getTenantId().equals(dashboard.getTenantId())) {
return false;
}
- if (!dashboard.isAssignedToCustomer(user.getCustomerId())) {
- return false;
- }
- return true;
+ return dashboard.isAssignedToCustomer(user.getCustomerId());
}
};
@@ -113,10 +117,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) {
return false;
}
- if (!user.getId().equals(userId)) {
- return false;
- }
- return true;
+ return user.getId().equals(userId);
}
};
@@ -132,10 +133,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return true;
}
- if (!user.getTenantId().equals(entity.getTenantId())) {
- return false;
- }
- return true;
+ return user.getTenantId().equals(entity.getTenantId());
}
};
@@ -151,10 +149,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return true;
}
- if (!user.getTenantId().equals(entity.getTenantId())) {
- return false;
- }
- return true;
+ return user.getTenantId().equals(entity.getTenantId());
}
};
}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 5a37c9c3aa..03c3bbd9f9 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -165,7 +165,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
- base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.3}"
+ base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.4}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
index 6625bdefc7..cc561e31a8 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
@@ -64,6 +64,7 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@@ -124,6 +125,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected String username;
protected TenantId tenantId;
+ protected CustomerId customerId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@@ -192,6 +194,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
customer.setTitle("Customer");
customer.setTenantId(tenantId);
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
+ customerId = savedCustomer.getId();
User customerUser = new User();
customerUser.setAuthority(Authority.CUSTOMER_USER);
@@ -254,9 +257,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
}
private Tenant savedDifferentTenant;
+ private Customer savedDifferentCustomer;
protected void loginDifferentTenant() throws Exception {
loginSysAdmin();
+ if (savedDifferentTenant != null) {
+ deleteDifferentTenant();
+ }
+
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
@@ -269,10 +277,27 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
createUserAndLogin(differentTenantAdmin, "testPassword");
}
+ protected void loginDifferentCustomer() throws Exception {
+ loginTenantAdmin();
+ Customer customer = new Customer();
+ customer.setTitle("Different customer");
+ savedDifferentCustomer = doPost("/api/customer", customer, Customer.class);
+ Assert.assertNotNull(savedDifferentCustomer);
+ User differentCustomerUser = new User();
+ differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
+ differentCustomerUser.setTenantId(tenantId);
+ differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
+ differentCustomerUser.setEmail("different_customer@thingsboard.org");
+
+ createUserAndLogin(differentCustomerUser, "testPassword");
+ }
+
protected void deleteDifferentTenant() throws Exception {
- loginSysAdmin();
- doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
- .andExpect(status().isOk());
+ if (savedDifferentTenant != null) {
+ loginSysAdmin();
+ doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
}
protected User createUserAndLogin(User user, String password) throws Exception {
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
new file mode 100644
index 0000000000..ce9b6916fc
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
@@ -0,0 +1,230 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmSeverity;
+import org.thingsboard.server.common.data.alarm.AlarmStatus;
+
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
+
+ public static final String TEST_ALARM_TYPE = "Test";
+
+ protected Device customerDevice;
+
+ @Before
+ public void setup() throws Exception {
+ loginTenantAdmin();
+
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("Test device");
+ device.setLabel("Label");
+ device.setType("Type");
+ device.setCustomerId(customerId);
+ customerDevice = doPost("/api/device", device, Device.class);
+
+ logout();
+ }
+
+ @After
+ public void teardown() throws Exception {
+ loginSysAdmin();
+ deleteDifferentTenant();
+ }
+
+ @Test
+ public void testCreateAlarmViaCustomer() throws Exception {
+ loginCustomerUser();
+ createAlarm(TEST_ALARM_TYPE);
+ logout();
+ }
+
+ @Test
+ public void testCreateAlarmViaTenant() throws Exception {
+ loginTenantAdmin();
+ createAlarm(TEST_ALARM_TYPE);
+ logout();
+ }
+
+ @Test
+ public void testUpdateAlarmViaCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ alarm.setSeverity(AlarmSeverity.MAJOR);
+ Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
+ Assert.assertNotNull(updatedAlarm);
+ Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
+ logout();
+ }
+
+ @Test
+ public void testUpdateAlarmViaTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ alarm.setSeverity(AlarmSeverity.MAJOR);
+ Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
+ Assert.assertNotNull(updatedAlarm);
+ Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
+ logout();
+ }
+
+ @Test
+ public void testUpdateAlarmViaDifferentTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ alarm.setSeverity(AlarmSeverity.MAJOR);
+ loginDifferentTenant();
+ doPost("/api/alarm", alarm).andExpect(status().isForbidden());
+ logout();
+ }
+
+ @Test
+ public void testUpdateAlarmViaDifferentCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ loginDifferentCustomer();
+ alarm.setSeverity(AlarmSeverity.MAJOR);
+ doPost("/api/alarm", alarm).andExpect(status().isForbidden());
+ logout();
+ }
+
+ @Test
+ public void testDeleteAlarmViaCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
+ logout();
+ }
+
+ @Test
+ public void testDeleteAlarmViaTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
+ logout();
+ }
+
+ @Test
+ public void testDeleteAlarmViaDifferentTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ loginDifferentTenant();
+ doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
+ logout();
+ }
+
+ @Test
+ public void testDeleteAlarmViaAnotherCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ loginDifferentCustomer();
+ doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
+ logout();
+ }
+
+ @Test
+ public void testClearAlarmViaCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
+ Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
+ logout();
+ }
+
+ @Test
+ public void testClearAlarmViaTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
+ Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
+ logout();
+ }
+
+ @Test
+ public void testAcknowledgeAlarmViaCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk());
+ Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus());
+ logout();
+ }
+
+ @Test
+ public void testClearAlarmViaDifferentCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ loginDifferentCustomer();
+ doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
+ logout();
+ }
+
+ @Test
+ public void testClearAlarmViaDifferentTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ loginDifferentTenant();
+ doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
+ logout();
+ }
+
+ @Test
+ public void testAcknowledgeAlarmViaDifferentCustomer() throws Exception {
+ loginCustomerUser();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ loginDifferentCustomer();
+ doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
+ logout();
+ }
+
+ @Test
+ public void testAcknowledgeAlarmViaDifferentTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ loginDifferentTenant();
+ doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
+ logout();
+ }
+
+ private Alarm createAlarm(String type) throws Exception {
+ Alarm alarm = Alarm.builder()
+ .tenantId(tenantId)
+ .customerId(customerId)
+ .originator(customerDevice.getId())
+ .status(AlarmStatus.ACTIVE_UNACK)
+ .severity(AlarmSeverity.CRITICAL)
+ .type(type)
+ .build();
+
+ alarm = doPost("/api/alarm", alarm, Alarm.class);
+ Assert.assertNotNull(alarm);
+
+ return alarm;
+ }
+}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
index 3b5ec02441..8c692ef8a2 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
@@ -16,10 +16,13 @@
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.awaitility.Awaitility;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@@ -29,6 +32,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
+import org.thingsboard.server.common.data.query.DynamicValue;
+import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
@@ -41,11 +46,13 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
+import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -299,6 +306,79 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
List deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
+ }
+
+ @Test
+ public void testFindEntityDataByQueryWithDynamicValue() throws Exception {
+ int numOfDevices = 2;
+
+ for (int i = 0; i < numOfDevices; i++) {
+ Device device = new Device();
+ String name = "Device" + i;
+ device.setName(name);
+ device.setType("default");
+ device.setLabel("testLabel" + (int) (Math.random() * 1000));
+
+ Device savedDevice1 = doPost("/api/device?accessToken=" + name, device, Device.class);
+ JsonNode content = JacksonUtil.toJsonNode("{\"alarmActiveTime\": 1" + i + "}");
+ doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice1.getUuidId() + "/SERVER_SCOPE", content)
+ .andExpect(status().isOk());
+ }
+ JsonNode content = JacksonUtil.toJsonNode("{\"dynamicValue\": 0}");
+ doPost("/api/plugins/telemetry/" + EntityType.TENANT.name() + "/" + tenantId.getId() + "/SERVER_SCOPE", content)
+ .andExpect(status().isOk());
+
+
+ DeviceTypeFilter filter = new DeviceTypeFilter();
+ filter.setDeviceType("default");
+ filter.setDeviceNameFilter("");
+
+ KeyFilter highTemperatureFilter = new KeyFilter();
+ highTemperatureFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "alarmActiveTime"));
+ NumericFilterPredicate predicate = new NumericFilterPredicate();
+
+ DynamicValue dynamicValue =
+ new DynamicValue<>(DynamicValueSourceType.CURRENT_TENANT, "dynamicValue");
+ FilterPredicateValue predicateValue = new FilterPredicateValue<>(0.0, null, dynamicValue);
+ predicate.setValue(predicateValue);
+ predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
+ highTemperatureFilter.setPredicate(predicate);
+
+ List keyFilters = Collections.singletonList(highTemperatureFilter);
+
+
+ EntityDataSortOrder sortOrder = new EntityDataSortOrder(
+ new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
+ );
+ EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
+
+ List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
+ List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmActiveTime"));
+
+ EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
+
+ Awaitility.await()
+ .alias("data by query")
+ .atMost(30, TimeUnit.SECONDS)
+ .until(() -> {
+ var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {});
+ var loadedEntities = new ArrayList<>(data.getData());
+ return loadedEntities.size() == numOfDevices;
+ });
+
+ var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference>() {});
+ var loadedEntities = new ArrayList<>(data.getData());
+
+ Assert.assertEquals(numOfDevices, loadedEntities.size());
+
+ for (int i = 0; i < numOfDevices; i++) {
+ var entity = loadedEntities.get(i);
+ String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
+ String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue();
+
+ Assert.assertEquals("Device" + i, name);
+ Assert.assertEquals("1" + i, alarmActiveTime);
+ }
}
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java
new file mode 100644
index 0000000000..bba390448c
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java
@@ -0,0 +1,23 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller.sql;
+
+import org.thingsboard.server.controller.BaseAlarmControllerTest;
+import org.thingsboard.server.dao.service.DaoSqlTest;
+
+@DaoSqlTest
+public class AlarmControllerSqlTest extends BaseAlarmControllerTest {
+}
diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
index 9392448c6c..5f7acd0422 100644
--- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
+++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
@@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@@ -80,6 +81,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
@@ -110,6 +112,7 @@ import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
+import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
@@ -331,6 +334,34 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertNotNull(jsonNode.get("test"));
}
+ @Test
+ public void testDeviceProfiles() throws Exception {
+ // 1
+ DeviceProfile deviceProfile = this.createDeviceProfile("ONE_MORE_DEVICE_PROFILE", null);
+ extendDeviceProfileData(deviceProfile);
+ edgeImitator.expectMessageAmount(1);
+ deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
+ Assert.assertTrue(edgeImitator.waitForMessages());
+ AbstractMessage latestMessage = edgeImitator.getLatestMessage();
+ Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
+ DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
+ Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
+ Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits());
+ Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits());
+
+ // 2
+ edgeImitator.expectMessageAmount(1);
+ doDelete("/api/deviceProfile/" + deviceProfile.getUuidId())
+ .andExpect(status().isOk());
+ Assert.assertTrue(edgeImitator.waitForMessages());
+ latestMessage = edgeImitator.getLatestMessage();
+ Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
+ deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
+ Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
+ Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits());
+ Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits());
+ }
+
@Test
public void testDevices() throws Exception {
// 1
@@ -385,6 +416,40 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
}
+ @Test
+ public void testDeviceReachedMaximumAllowedOnCloud() throws Exception {
+ // update tenant profile configuration
+ loginSysAdmin();
+ TenantProfile tenantProfile = doGet("/api/tenantProfile/" + savedTenant.getTenantProfileId().getId(), TenantProfile.class);
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration();
+ profileConfiguration.setMaxDevices(1);
+ tenantProfile.getProfileData().setConfiguration(profileConfiguration);
+ doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class);
+
+ loginTenantAdmin();
+
+ UUID uuid = Uuids.timeBased();
+
+ UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
+ DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder();
+ deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
+ deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
+ deviceUpdateMsgBuilder.setName("Edge Device");
+ deviceUpdateMsgBuilder.setType("default");
+ deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
+ uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build());
+
+ edgeImitator.expectResponsesAmount(1);
+
+ edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
+
+ Assert.assertTrue(edgeImitator.waitForResponses());
+
+ UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
+ Assert.assertTrue(latestResponseMsg.getSuccess());
+ }
+
@Test
public void testAssets() throws Exception {
// 1
@@ -547,8 +612,6 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
ruleChainMetaData.addConnectionInfo(0, 2, "fail");
ruleChainMetaData.addConnectionInfo(1, 2, "success");
- ruleChainMetaData.addRuleChainConnectionInfo(2, edge.getRootRuleChainId(), "success", mapper.createObjectNode());
-
doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
}
diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
index 89c61f5a14..cfb652dd9f 100644
--- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
+++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
@@ -88,6 +88,9 @@ public class EdgeImitator {
@Getter
private List downlinkMsgs;
+ @Getter
+ private UplinkResponseMsg latestResponseMsg;
+
private boolean connected = false;
public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException {
@@ -132,6 +135,7 @@ public class EdgeImitator {
private void onUplinkResponse(UplinkResponseMsg msg) {
log.info("onUplinkResponse: {}", msg);
+ latestResponseMsg = msg;
responsesLatch.countDown();
}
diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
index dcc8f35ba3..9d96fe9d9c 100644
--- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
@@ -25,6 +25,7 @@ import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
+import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants;
@@ -35,6 +36,7 @@ import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.PageData;
+import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
@@ -240,16 +242,27 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(mapper.valueToTree(configuration1));
- rootMetaData.setNodes(Collections.singletonList(ruleNode1));
+ RuleNode ruleNode12 = new RuleNode();
+ ruleNode12.setName("Simple Rule Node 1");
+ ruleNode12.setType(org.thingsboard.rule.engine.flow.TbRuleChainInputNode.class.getName());
+ ruleNode12.setDebugMode(true);
+ TbRuleChainInputNodeConfiguration configuration12 = new TbRuleChainInputNodeConfiguration();
+ configuration12.setRuleChainId(secondaryRuleChain.getId().getId().toString());
+ ruleNode12.setConfiguration(mapper.valueToTree(configuration12));
+
+ rootMetaData.setNodes(Arrays.asList(ruleNode1, ruleNode12));
rootMetaData.setFirstNodeIndex(0);
- rootMetaData.addRuleChainConnectionInfo(0, secondaryRuleChain.getId(), "Success", mapper.createObjectNode());
+ NodeConnectionInfo connection = new NodeConnectionInfo();
+ connection.setFromIndex(0);
+ connection.setToIndex(1);
+ connection.setType("Success");
+ rootMetaData.setConnections(Collections.singletonList(connection));
rootMetaData = saveRuleChainMetaData(rootMetaData);
Assert.assertNotNull(rootMetaData);
rootRuleChain = getRuleChain(rootRuleChain.getId());
Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId());
-
RuleChainMetaData secondaryMetaData = new RuleChainMetaData();
secondaryMetaData.setRuleChainId(secondaryRuleChain.getId());
diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
index 51b4468d01..ea95923275 100644
--- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
+++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
@@ -42,7 +42,7 @@ public interface EdgeService {
Optional findEdgeByRoutingKey(TenantId tenantId, String routingKey);
- Edge saveEdge(Edge edge, boolean doValidate);
+ Edge saveEdge(Edge edge);
Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId);
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java
index 0881b01c93..73caea088d 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java
@@ -16,11 +16,15 @@
package org.thingsboard.server.common.data.device.profile.lwm2m;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import lombok.AllArgsConstructor;
import lombok.Data;
+import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
@Data
+@NoArgsConstructor
+@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
public class OtherConfiguration extends PowerSavingConfiguration {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java
index 7d0f32d298..cb6f4e7695 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java
@@ -15,13 +15,16 @@
*/
package org.thingsboard.server.common.data.device.profile.lwm2m;
+import lombok.AllArgsConstructor;
import lombok.Data;
-import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes;
+import lombok.NoArgsConstructor;
import java.util.Map;
import java.util.Set;
@Data
+@NoArgsConstructor
+@AllArgsConstructor
public class TelemetryMappingConfiguration {
private Map keyName;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
index d51725f153..7bd19e2441 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
@@ -59,15 +59,4 @@ public class RuleChainMetaData {
}
connections.add(connectionInfo);
}
- public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type, JsonNode additionalInfo) {
- RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo();
- connectionInfo.setFromIndex(fromIndex);
- connectionInfo.setTargetRuleChainId(targetRuleChainId);
- connectionInfo.setType(type);
- connectionInfo.setAdditionalInfo(additionalInfo);
- if (ruleChainConnections == null) {
- ruleChainConnections = new ArrayList<>();
- }
- ruleChainConnections.add(connectionInfo);
- }
}
diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
index 45e5009a7b..3d4a4ee053 100644
--- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
+++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
@@ -153,7 +153,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
private String validatePayload(UUID sessionId, Request inbound, boolean isEmptyPayloadAllowed) throws AdaptorException {
String payload = inbound.getPayloadString();
if (payload == null) {
- log.warn("[{}] Payload is empty!", sessionId);
+ log.debug("[{}] Payload is empty!", sessionId);
if (!isEmptyPayloadAllowed) {
throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
}
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
index 15bf71b917..886ef918f3 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
@@ -27,6 +27,7 @@ import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager;
+import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import org.eclipse.leshan.server.model.LwM2mBootstrapModelProvider;
import org.eclipse.leshan.server.model.StandardBootstrapModelProvider;
import org.eclipse.leshan.server.security.BootstrapSecurityStore;
@@ -35,6 +36,7 @@ import org.eclipse.leshan.server.security.SecurityInfo;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapConfigStoreTaskProvider;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore;
+import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapTaskProvider;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.ArrayList;
@@ -48,10 +50,10 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LO
@Slf4j
public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
- private BootstrapSecurityStore bsSecurityStore;
- private SecurityChecker securityChecker;
- private BootstrapTaskProvider tasksProvider;
- private LwM2mBootstrapModelProvider modelProvider;
+ private final BootstrapSecurityStore bsSecurityStore;
+ private final SecurityChecker securityChecker;
+ private final LwM2MBootstrapTaskProvider tasksProvider;
+ private final LwM2mBootstrapModelProvider modelProvider;
private TransportService transportService;
/**
@@ -73,7 +75,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
* @param securityChecker used to accept or refuse new {@link BootstrapSession}.
*/
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker,
- BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) {
+ LwM2MBootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) {
super(bsSecurityStore, securityChecker, tasksProvider, modelProvider);
this.bsSecurityStore = bsSecurityStore;
this.securityChecker = securityChecker;
@@ -100,7 +102,12 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
}
DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized);
if (authorized) {
- this.sendLogs (request.getEndpointName(),
+ try {
+ this.tasksProvider.put(session.getEndpoint());
+ } catch (InvalidConfigurationException e){
+ log.error("Failed put to lwM2MBootstrapSessionClients by endpoint [{}]", request.getEndpointName(), e);
+ }
+ this.sendLogs(request.getEndpointName(),
String.format("%s: Bootstrap session started...", LOG_LWM2M_INFO, request.getEndpointName()));
}
return session;
@@ -108,7 +115,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
@Override
public boolean hasConfigFor(BootstrapSession session) {
- BootstrapTaskProvider.Tasks firstTasks = tasksProvider.getTasks(session, null);
+ BootstrapTaskProvider.Tasks firstTasks = this.tasksProvider.getTasks(session, null);
if (firstTasks == null) {
return false;
}
@@ -147,7 +154,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
return requestsToSend.remove(0);
} else {
if (session.hasMoreTasks()) {
- BootstrapTaskProvider.Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses());
+ BootstrapTaskProvider.Tasks nextTasks = this.tasksProvider.getTasks(session, session.getResponses());
if (nextTasks == null) {
session.setMoreTasks(false);
return new BootstrapFinishRequest();
@@ -178,6 +185,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
// on success for bootstrap finish request we stop the session
this.sendLogs(bsSession.getEndpoint(),
String.format("%s: receives success response for bootstrap finish.", LOG_LWM2M_INFO));
+ this.tasksProvider.remove(bsSession.getEndpoint());
return BootstrapPolicy.finished();
}
}
@@ -199,6 +207,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
// on response error for bootstrap finish request we stop the session
this.sendLogs(bsSession.getEndpoint(),
String.format("%s: error response for request bootstrap finish. Stop the session: %s", LOG_LWM2M_ERROR, bsSession.toString()));
+ this.tasksProvider.remove(bsSession.getEndpoint());
return BootstrapPolicy.failed();
}
}
@@ -215,12 +224,14 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
@Override
public void end(BootstrapSession bsSession) {
this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO));
+ this.tasksProvider.remove(bsSession.getEndpoint());
}
@Override
public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) {
this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session failed because of %s", LOG_LWM2M_ERROR,
cause.toString()));
+ this.tasksProvider.remove(bsSession.getEndpoint());
}
private void sendLogs(String endpointName, String logMsg) {
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapClientInstanceIds.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapClientInstanceIds.java
new file mode 100644
index 0000000000..85aee2b9b9
--- /dev/null
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapClientInstanceIds.java
@@ -0,0 +1,31 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.transport.lwm2m.bootstrap.store;
+
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+public class LwM2MBootstrapClientInstanceIds {
+
+ /**
+ * Map
+ */
+ private Map securityInstances = new HashMap<>();
+ private Map serverInstances = new HashMap<>();
+}
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
index 9100df57a1..50aff2a5bd 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
@@ -30,8 +30,8 @@ import org.eclipse.leshan.core.response.LwM2mResponse;
import org.eclipse.leshan.server.bootstrap.BootstrapConfig;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.BootstrapSession;
-import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.BootstrapUtil;
+import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -42,28 +42,36 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.stream.Collectors;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest;
+import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_DEFAULT_SHORT_ID;
@Slf4j
-public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvider {
+public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTaskProvider {
+
+ protected final ReadWriteLock readWriteLock;
+ protected final Lock writeLock;
private BootstrapConfigStore store;
private Map supportedObjects;
/**
- * Map
+ * Map
*/
- protected Map securityInstances;
- protected Map serverInstances;
- protected Integer bootstrapServerIdOld;
- protected Integer bootstrapServerIdNew;
+ protected Map lwM2MBootstrapSessionClients;
public LwM2MBootstrapConfigStoreTaskProvider(BootstrapConfigStore store) {
this.store = store;
+ this.lwM2MBootstrapSessionClients = new ConcurrentHashMap<>();
+ readWriteLock = new ReentrantReadWriteLock();
+ writeLock = readWriteLock.writeLock();
}
@Override
@@ -91,13 +99,13 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0);
if (discoverResponse.isSuccess()) {
this.initAfterBootstrapDiscover(discoverResponse);
- findSecurityInstanceId(discoverResponse.getObjectLinks());
+ findSecurityInstanceId(discoverResponse.getObjectLinks(), session.getEndpoint());
} else {
log.warn(
"Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}",
discoverResponse, session);
}
- if (this.securityInstances.get(0) == null) {
+ if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID) == null) {
log.error(
"Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}",
discoverResponse, session);
@@ -109,8 +117,12 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
return tasks;
}
BootstrapReadResponse readResponse = (BootstrapReadResponse) previousResponse.get(0);
+ Integer bootstrapServerIdOld = null;
if (readResponse.isSuccess()) {
- findServerInstanceId(readResponse);
+ findServerInstanceId(readResponse, session.getEndpoint());
+ if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().size() > 0 && this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getServerInstances().size() > 0) {
+ bootstrapServerIdOld = this.findBootstrapServerId(session.getEndpoint());
+ }
} else {
log.warn(
"Bootstrap ReadResponse return error {} : to continue bootstrap session without find Server Instance Id. {}",
@@ -118,7 +130,8 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
}
// create requests from config
tasks.requestsToSend = this.toRequests(config,
- config.contentFormat != null ? config.contentFormat : session.getContentFormat());
+ config.contentFormat != null ? config.contentFormat : session.getContentFormat(),
+ bootstrapServerIdOld, session.getEndpoint());
} else {
// create requests from config
tasks.requestsToSend = BootstrapUtil.toRequests(config,
@@ -132,9 +145,15 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
return config.autoIdForSecurityObject;
}
- protected void findSecurityInstanceId(Link[] objectLinks) {
+ /**
+ * "Short Server ID": This Resource MUST be set when the Bootstrap-Server Resource has a value of 'false'.
+ * The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.
+ * "Short Server ID":
+ * - Link Instance (lwm2m Server) hase linkParams with key = "ssid" value = "shortId" (ver lvm2m = 1.1).
+ * - Link Instance (bootstrap Server) hase not linkParams with key = "ssid" (ver lvm2m = 1.1).
+ */
+ protected void findSecurityInstanceId(Link[] objectLinks, String endpoint) {
log.info("Object after discover: [{}]", objectLinks);
- this.securityInstances = new HashMap<>();
for (Link link : objectLinks) {
if (link.getUriReference().startsWith("/0/")) {
try {
@@ -142,15 +161,15 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
if (path.isObjectInstance()) {
if (link.getLinkParams().containsKey("ssid")) {
int serverId = Integer.parseInt(link.getLinkParams().get("ssid").getUnquoted());
- if (!this.securityInstances.containsKey(serverId)) {
- this.securityInstances.put(serverId, path.getObjectInstanceId());
+ if (!lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(serverId)) {
+ lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId());
} else {
log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId());
}
- this.securityInstances.put(Integer.valueOf(link.getLinkParams().get("ssid").getUnquoted()), path.getObjectInstanceId());
+ lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(Integer.valueOf(link.getLinkParams().get("ssid").getUnquoted()), path.getObjectInstanceId());
} else {
- if (!this.securityInstances.containsKey(0)) {
- this.securityInstances.put(0, path.getObjectInstanceId());
+ if (!this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(0)) {
+ this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID, path.getObjectInstanceId());
} else {
log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId());
}
@@ -164,8 +183,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
}
}
- protected void findServerInstanceId(BootstrapReadResponse readResponse) {
- this.serverInstances = new HashMap<>();
+ protected void findServerInstanceId(BootstrapReadResponse readResponse, String endpoint) {
try {
((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> {
var shId = OPAQUE.equals(instance.getResource(0).getType()) ? new BigInteger((byte[]) instance.getResource(0).getValue()).intValue() : instance.getResource(0).getValue();
@@ -175,23 +193,22 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
} else {
shortId = (int) shId;
}
- serverInstances.put(shortId, instance.getId());
+ this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().put(shortId, instance.getId());
});
} catch (Exception e) {
log.error("Failed find Server Instance Id. ", e);
}
- if (this.securityInstances != null && this.securityInstances.size() > 0 && this.serverInstances != null && this.serverInstances.size() > 0) {
- this.findBootstrapServerId();
- }
}
- protected void findBootstrapServerId() {
- Map filteredMap = this.serverInstances.entrySet()
- .stream().filter(x -> !this.securityInstances.containsKey(x.getKey()))
+ protected Integer findBootstrapServerId(String endpoint) {
+ Integer bootstrapServerIdOld = null;
+ Map filteredMap = this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().entrySet()
+ .stream().filter(x -> !this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(x.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (filteredMap.size() > 0) {
- this.bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get();
+ bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get();
}
+ return bootstrapServerIdOld;
}
public BootstrapConfigStore getStore() {
@@ -213,46 +230,51 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
public List> toRequests(BootstrapConfig bootstrapConfig,
- ContentFormat contentFormat) {
+ ContentFormat contentFormat,
+ Integer bootstrapServerIdOld,
+ String endpoint) {
List> requests = new ArrayList<>();
Set pathsDelete = new HashSet<>();
List> requestsWrite = new ArrayList<>();
boolean isBsServer = false;
boolean isLwServer = false;
- /** Map */
+ /** Map */
Map instances = new HashMap<>();
+ Integer bootstrapServerIdNew = null;
// handle security
- int id = 0;
+ int lwm2mSecurityInstanceId = 0;
+ int bootstrapSecurityInstanceId = this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID);
for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) {
if (security.bootstrapServer) {
- requestsWrite.add(toWriteRequest(this.securityInstances.get(0), security, contentFormat));
+ requestsWrite.add(toWriteRequest(bootstrapSecurityInstanceId, security, contentFormat));
isBsServer = true;
- this.bootstrapServerIdNew = security.serverId;
- instances.put(security.serverId, this.securityInstances.get(0));
+ bootstrapServerIdNew = security.serverId;
+ instances.put(security.serverId, bootstrapSecurityInstanceId);
} else {
- if (id == this.securityInstances.get(0)) {
- id++;
+ if (lwm2mSecurityInstanceId == bootstrapSecurityInstanceId) {
+ lwm2mSecurityInstanceId++;
}
- requestsWrite.add(toWriteRequest(id, security, contentFormat));
- instances.put(security.serverId, id);
+ requestsWrite.add(toWriteRequest(lwm2mSecurityInstanceId, security, contentFormat));
+ instances.put(security.serverId, lwm2mSecurityInstanceId);
isLwServer = true;
- if (!isBsServer && this.securityInstances.containsKey(security.serverId) && id != this.securityInstances.get(security.serverId)) {
- pathsDelete.add("/0/" + this.securityInstances.get(security.serverId));
+ if (!isBsServer && this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(security.serverId) &&
+ lwm2mSecurityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId)) {
+ pathsDelete.add("/0/" + this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().get(security.serverId));
}
/**
* If there is an instance in the serverInstances with serverId which we replace in the securityInstances
*/
// find serverId in securityInstances by id (instance)
Integer serverIdOld = null;
- for (Map.Entry entry : this.securityInstances.entrySet()) {
- if (entry.getValue().equals(id)) {
+ for (Map.Entry entry : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().entrySet()) {
+ if (entry.getValue().equals(lwm2mSecurityInstanceId)) {
serverIdOld = entry.getKey();
}
}
- if (!isBsServer && serverIdOld != null && this.serverInstances.containsKey(serverIdOld)) {
- pathsDelete.add("/1/" + this.serverInstances.get(serverIdOld));
+ if (!isBsServer && serverIdOld != null && this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(serverIdOld)) {
+ pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(serverIdOld));
}
- id++;
+ lwm2mSecurityInstanceId++;
}
}
// handle server
@@ -261,12 +283,13 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
requestsWrite.add(toWriteRequest(securityInstanceId, server.getValue(), contentFormat));
if (!isBsServer) {
/** Delete instance if bootstrapServerIdNew not equals bootstrapServerIdOld or securityInstanceBsIdNew not equals serverInstanceBsIdOld */
- if (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew &&
- (this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) {
- pathsDelete.add("/1/" + this.serverInstances.get(this.bootstrapServerIdOld));
+ if (bootstrapServerIdNew != null && server.getValue().shortId == bootstrapServerIdNew &&
+ (bootstrapServerIdNew != bootstrapServerIdOld || securityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(bootstrapServerIdOld))) {
+ pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(bootstrapServerIdOld));
/** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */
- } else if (this.serverInstances.containsKey(server.getValue().shortId) && securityInstanceId != this.serverInstances.get(server.getValue().shortId)) {
- pathsDelete.add("/1/" + this.serverInstances.get(server.getValue().shortId));
+ } else if (this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(server.getValue().shortId) &&
+ securityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(server.getValue().shortId)) {
+ pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(server.getValue().shortId));
}
}
}
@@ -288,10 +311,31 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
return (requests);
}
+
private void initSupportedObjectsDefault() {
this.supportedObjects = new HashMap<>();
this.supportedObjects.put(0, "1.1");
this.supportedObjects.put(1, "1.1");
this.supportedObjects.put(2, "1.0");
}
+
+ @Override
+ public void remove(String endpoint) {
+ writeLock.lock();
+ try {
+ this.lwM2MBootstrapSessionClients.remove(endpoint);
+ } finally {
+ writeLock.unlock();
+ }
+ }
+
+ @Override
+ public void put(String endpoint) throws InvalidConfigurationException {
+ writeLock.lock();
+ try {
+ this.lwM2MBootstrapSessionClients.put(endpoint, new LwM2MBootstrapClientInstanceIds());
+ } finally {
+ writeLock.unlock();
+ }
+ }
}
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapTaskProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapTaskProvider.java
new file mode 100644
index 0000000000..6bc792fed7
--- /dev/null
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapTaskProvider.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.transport.lwm2m.bootstrap.store;
+
+import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
+import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
+
+public interface LwM2MBootstrapTaskProvider extends BootstrapTaskProvider {
+
+ void put(String endpoint) throws InvalidConfigurationException;
+
+ void remove(String endpoint);
+}
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
index 7860fd94a5..9b49030f7e 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
@@ -20,11 +20,15 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.scandium.config.DtlsConfig;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite;
+import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder;
+import org.eclipse.leshan.core.request.SendRequest;
import org.eclipse.leshan.server.californium.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
+import org.eclipse.leshan.server.registration.Registration;
+import org.eclipse.leshan.server.send.SendListener;
import org.springframework.stereotype.Component;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.DataConstants;
@@ -40,6 +44,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PreDestroy;
import java.security.cert.X509Certificate;
+import java.util.Map;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY;
@@ -94,6 +99,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
this.server.getRegistrationService().addListener(lhServerCertListener.registrationListener);
this.server.getPresenceService().addListener(lhServerCertListener.presenceListener);
this.server.getObservationService().addListener(lhServerCertListener.observationListener);
+ this.server.getSendService().addListener(lhServerCertListener.sendListener);
log.info("Started LwM2M transport server.");
}
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
index 5c0e225f8b..4d4af68fad 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
@@ -16,9 +16,11 @@
package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j;
+import org.eclipse.leshan.core.node.LwM2mNode;
import org.eclipse.leshan.core.observation.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.SingleObservation;
+import org.eclipse.leshan.core.request.SendRequest;
import org.eclipse.leshan.core.response.ObserveCompositeResponse;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.server.observation.ObservationListener;
@@ -26,9 +28,11 @@ import org.eclipse.leshan.server.queue.PresenceListener;
import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationListener;
import org.eclipse.leshan.server.registration.RegistrationUpdate;
+import org.eclipse.leshan.server.send.SendListener;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import java.util.Collection;
+import java.util.Map;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId;
@@ -119,4 +123,14 @@ public class LwM2mServerListener {
log.trace("Successful start newObservation {}.", ((SingleObservation)observation).getPath());
}
};
+
+ public final SendListener sendListener = new SendListener() {
+
+ @Override
+ public void dataReceived(Registration registration, Map map, SendRequest sendRequest) {
+ if (registration != null) {
+ service.onUpdateValueWithSendRequest(registration, sendRequest);
+ }
+ }
+ };
}
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
index 05ce126c7e..d73bd646c3 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
@@ -33,11 +33,7 @@ import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.observation.Observation;
-import org.eclipse.leshan.core.request.CreateRequest;
-import org.eclipse.leshan.core.request.ObserveRequest;
-import org.eclipse.leshan.core.request.ReadRequest;
-import org.eclipse.leshan.core.request.WriteCompositeRequest;
-import org.eclipse.leshan.core.request.WriteRequest;
+import org.eclipse.leshan.core.request.*;
import org.eclipse.leshan.core.request.WriteRequest.Mode;
import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ReadCompositeResponse;
@@ -98,16 +94,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Random;
-import java.util.Set;
-import java.util.UUID;
+import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -347,12 +334,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE, responseCode);
}
}
- if (clientContext.awake(lwM2MClient)) {
- // clientContext.awake calls clientContext.update
- log.debug("[{}] Device is awake", lwM2MClient.getEndpoint());
- } else {
- clientContext.update(lwM2MClient);
- }
+ tryAwake(lwM2MClient);
}
}
@@ -373,12 +355,37 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
}
});
clientContext.update(lwM2MClient);
- if (clientContext.awake(lwM2MClient)) {
- // clientContext.awake calls clientContext.update
- log.debug("[{}] Device is awake", lwM2MClient.getEndpoint());
- } else {
- clientContext.update(lwM2MClient);
+ tryAwake(lwM2MClient);
+ }
+ }
+
+ /**
+ * Sending updated value to thingsboard from SendListener.dataReceived: object, instance, SingleResource or MultipleResource
+ *
+ * @param registration - Registration LwM2M Client
+ * @param sendRequest - sendRequest
+ */
+ @Override
+ public void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest) {
+ for(var entry : sendRequest.getNodes().entrySet()) {
+ LwM2mPath path = entry.getKey();
+ LwM2mNode node = entry.getValue();
+ LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint());
+ String stringPath = convertObjectIdToVersionedId(path.toString(), registration);
+ ObjectModel objectModelVersion = lwM2MClient.getObjectModel(stringPath, modelProvider);
+ if (objectModelVersion != null) {
+ if (node instanceof LwM2mObject) {
+ LwM2mObject lwM2mObject = (LwM2mObject) node;
+ this.updateObjectResourceValue(lwM2MClient, lwM2mObject, stringPath, 0);
+ } else if (node instanceof LwM2mObjectInstance) {
+ LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) node;
+ this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, stringPath, 0);
+ } else if (node instanceof LwM2mResource) {
+ LwM2mResource lwM2mResource = (LwM2mResource) node;
+ this.updateResourcesValue(lwM2MClient, lwM2mResource, stringPath, Mode.UPDATE, 0);
+ }
}
+ tryAwake(lwM2MClient);
}
}
@@ -989,4 +996,14 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
client.unlock();
}
}
+
+ private void tryAwake(LwM2mClient lwM2MClient) {
+ if (clientContext.awake(lwM2MClient)) {
+ // clientContext.awake calls clientContext.update
+ log.debug("[{}] Device is awake", lwM2MClient.getEndpoint());
+ } else {
+ clientContext.update(lwM2MClient);
+ }
+ }
+
}
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
index 475d834c60..332bb6ccad 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.uplink;
import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.CreateRequest;
+import org.eclipse.leshan.core.request.SendRequest;
import org.eclipse.leshan.core.request.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.ReadCompositeResponse;
@@ -46,6 +47,8 @@ public interface LwM2mUplinkMsgHandler {
void onUpdateValueAfterReadCompositeResponse(Registration registration, ReadCompositeResponse response);
+ void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest);
+
void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile);
void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt);
diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java
index e415858755..ca001e3b83 100644
--- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java
+++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java
@@ -92,6 +92,7 @@ public class LwM2MTransportUtil {
public static final String LOG_LWM2M_INFO = "info";
public static final String LOG_LWM2M_ERROR = "error";
public static final String LOG_LWM2M_WARN = "warn";
+ public static final int BOOTSTRAP_DEFAULT_SHORT_ID = 0;
public enum LwM2MClientStrategy {
CLIENT_STRATEGY_1(1, "Read only resources marked as observation"),
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
index a07982d718..e80f735d89 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
@@ -242,9 +242,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName);
ctx.close();
}
- } catch (RuntimeException | AdaptorException e) {
+ } catch (RuntimeException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close();
+ } catch (AdaptorException e) {
+ log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
+ ctx.close();
}
break;
case PINGREQ:
@@ -354,9 +357,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
default:
ack(ctx, msgId);
}
- } catch (RuntimeException | AdaptorException e) {
+ } catch (RuntimeException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close();
+ } catch (AdaptorException e) {
+ log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
+ ctx.close();
}
}
@@ -444,7 +450,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ack(ctx, msgId);
}
} catch (AdaptorException e) {
- log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
+ log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId);
ctx.close();
}
@@ -749,7 +755,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
}
} catch (Exception e) {
- log.warn("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
+ log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
}
}
if (!activityReported) {
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
index 73c0e9a32f..75c76c0cdb 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
@@ -61,7 +61,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try {
return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
} catch (IllegalStateException | JsonSyntaxException ex) {
- log.warn("Failed to decode post telemetry request", ex);
+ log.debug("Failed to decode post telemetry request", ex);
throw new AdaptorException(ex);
}
}
@@ -72,7 +72,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try {
return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
} catch (IllegalStateException | JsonSyntaxException ex) {
- log.warn("Failed to decode post attributes request", ex);
+ log.debug("Failed to decode post attributes request", ex);
throw new AdaptorException(ex);
}
}
@@ -83,7 +83,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try {
return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload);
} catch (IllegalStateException | JsonSyntaxException ex) {
- log.warn("Failed to decode claim device request", ex);
+ log.debug("Failed to decode claim device request", ex);
throw new AdaptorException(ex);
}
}
@@ -164,7 +164,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try {
return new JsonParser().parse(payload);
} catch (JsonSyntaxException ex) {
- log.warn("Payload is in incorrect format: {}", payload);
+ log.debug("Payload is in incorrect format: {}", payload);
throw new AdaptorException(ex);
}
}
@@ -186,7 +186,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
}
return result.build();
} catch (RuntimeException e) {
- log.warn("Failed to decode get attributes request", e);
+ log.debug("Failed to decode get attributes request", e);
throw new AdaptorException(e);
}
}
@@ -198,7 +198,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
String payload = inbound.payload().toString(UTF8);
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build();
} catch (RuntimeException e) {
- log.warn("Failed to decode rpc response", e);
+ log.debug("Failed to decode rpc response", e);
throw new AdaptorException(e);
}
}
@@ -210,7 +210,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase);
return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
} catch (IllegalStateException | JsonSyntaxException ex) {
- log.warn("Failed to decode to server rpc request", ex);
+ log.debug("Failed to decode to server rpc request", ex);
throw new AdaptorException(ex);
}
}
@@ -259,7 +259,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException {
String payload = payloadData.toString(UTF8);
if (payload == null) {
- log.warn("[{}] Payload is empty!", sessionId);
+ log.debug("[{}] Payload is empty!", sessionId);
if (!isEmptyPayloadAllowed) {
throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
}
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
index 1f0aef1c64..175bf17c02 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
@@ -52,7 +52,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try {
return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
} catch (Exception e) {
- log.warn("Failed to decode post telemetry request", e);
+ log.debug("Failed to decode post telemetry request", e);
throw new AdaptorException(e);
}
}
@@ -65,7 +65,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try {
return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor)));
} catch (Exception e) {
- log.warn("Failed to decode post attributes request", e);
+ log.debug("Failed to decode post attributes request", e);
throw new AdaptorException(e);
}
}
@@ -76,7 +76,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try {
return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes);
} catch (InvalidProtocolBufferException e) {
- log.warn("Failed to decode claim device request", e);
+ log.debug("Failed to decode claim device request", e);
throw new AdaptorException(e);
}
}
@@ -89,7 +89,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase);
return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId);
} catch (InvalidProtocolBufferException e) {
- log.warn("Failed to decode get attributes request", e);
+ log.debug("Failed to decode get attributes request", e);
throw new AdaptorException(e);
}
}
@@ -105,7 +105,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor));
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build();
} catch (Exception e) {
- log.warn("Failed to decode rpc response", e);
+ log.debug("Failed to decode rpc response", e);
throw new AdaptorException(e);
}
}
@@ -118,7 +118,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase);
return ProtoConverter.convertToServerRpcRequest(bytes, requestId);
} catch (InvalidProtocolBufferException e) {
- log.warn("Failed to decode to server rpc request", e);
+ log.debug("Failed to decode to server rpc request", e);
throw new AdaptorException(e);
}
}
@@ -129,7 +129,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try {
return ProtoConverter.convertToProvisionRequestMsg(bytes);
} catch (InvalidProtocolBufferException ex) {
- log.warn("Failed to decode provision request", ex);
+ log.debug("Failed to decode provision request", ex);
throw new AdaptorException(ex);
}
}
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java
index c7a70c6d44..c6f605c9dd 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.transport.mqtt.limits;
+import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.haproxy.HAProxyMessage;
@@ -60,7 +61,15 @@ public class ProxyIpFilter extends ChannelInboundHandlerAdapter {
private void closeChannel(ChannelHandlerContext ctx) {
while (ctx.pipeline().last() != this) {
- ctx.pipeline().removeLast();
+ ChannelHandler handler = ctx.pipeline().removeLast();
+ if (handler instanceof ChannelInboundHandlerAdapter) {
+ try {
+ ((ChannelInboundHandlerAdapter) handler).channelUnregistered(ctx);
+ } catch (Exception e) {
+ log.error("Failed to unregister channel: [{}]", ctx, e);
+ }
+ }
+
}
ctx.pipeline().remove(this);
ctx.close();
diff --git a/common/util/pom.xml b/common/util/pom.xml
index 3c20fb0b8f..147497ef04 100644
--- a/common/util/pom.xml
+++ b/common/util/pom.xml
@@ -84,6 +84,10 @@
awaitility
test
+
+ org.thingsboard.common
+ data
+
diff --git a/common/util/src/main/java/org/thingsboard/common/util/KvUtil.java b/common/util/src/main/java/org/thingsboard/common/util/KvUtil.java
new file mode 100644
index 0000000000..ba86fda769
--- /dev/null
+++ b/common/util/src/main/java/org/thingsboard/common/util/KvUtil.java
@@ -0,0 +1,89 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.common.util;
+
+import org.thingsboard.server.common.data.kv.KvEntry;
+
+public class KvUtil {
+
+ public static String getStringValue(KvEntry entry) {
+ switch (entry.getDataType()) {
+ case LONG:
+ return entry.getLongValue().map(String::valueOf).orElse(null);
+ case DOUBLE:
+ return entry.getDoubleValue().map(String::valueOf).orElse(null);
+ case BOOLEAN:
+ return entry.getBooleanValue().map(String::valueOf).orElse(null);
+ case STRING:
+ return entry.getStrValue().orElse("");
+ case JSON:
+ return entry.getJsonValue().orElse("");
+ default:
+ return null;
+ }
+ }
+
+ public static Double getDoubleValue(KvEntry entry) {
+ switch (entry.getDataType()) {
+ case LONG:
+ return entry.getLongValue().map(Long::doubleValue).orElse(null);
+ case DOUBLE:
+ return entry.getDoubleValue().orElse(null);
+ case BOOLEAN:
+ return entry.getBooleanValue().map(e -> e ? 1.0 : 0).orElse(null);
+ case STRING:
+ try {
+ return Double.parseDouble(entry.getStrValue().orElse(""));
+ } catch (RuntimeException e) {
+ return null;
+ }
+ case JSON:
+ try {
+ return Double.parseDouble(entry.getJsonValue().orElse(""));
+ } catch (RuntimeException e) {
+ return null;
+ }
+ default:
+ return null;
+ }
+ }
+
+ public static Boolean getBoolValue(KvEntry entry) {
+ switch (entry.getDataType()) {
+ case LONG:
+ return entry.getLongValue().map(e -> e != 0).orElse(null);
+ case DOUBLE:
+ return entry.getDoubleValue().map(e -> e != 0).orElse(null);
+ case BOOLEAN:
+ return entry.getBooleanValue().orElse(null);
+ case STRING:
+ try {
+ return Boolean.parseBoolean(entry.getStrValue().orElse(""));
+ } catch (RuntimeException e) {
+ return null;
+ }
+ case JSON:
+ try {
+ return Boolean.parseBoolean(entry.getJsonValue().orElse(""));
+ } catch (RuntimeException e) {
+ return null;
+ }
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
index 08f4576449..714e131025 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
@@ -25,9 +25,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
-import org.springframework.util.StringUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
@@ -49,9 +47,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
-import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
@@ -59,7 +55,6 @@ import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@@ -82,10 +77,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private AlarmDao alarmDao;
@Autowired
- private TenantDao tenantDao;
+ private EntityService entityService;
@Autowired
- private EntityService entityService;
+ private DataValidator alarmDataValidator;
protected ExecutorService readResultsProcessingExecutor;
@@ -412,32 +407,4 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
ListenableFuture entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId());
return Futures.transform(entity, function, readResultsProcessingExecutor);
}
-
- private DataValidator alarmDataValidator =
- new DataValidator<>() {
-
- @Override
- protected void validateDataImpl(TenantId tenantId, Alarm alarm) {
- if (StringUtils.isEmpty(alarm.getType())) {
- throw new DataValidationException("Alarm type should be specified!");
- }
- if (alarm.getOriginator() == null) {
- throw new DataValidationException("Alarm originator should be specified!");
- }
- if (alarm.getSeverity() == null) {
- throw new DataValidationException("Alarm severity should be specified!");
- }
- if (alarm.getStatus() == null) {
- throw new DataValidationException("Alarm status should be specified!");
- }
- if (alarm.getTenantId() == null) {
- throw new DataValidationException("Alarm should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(alarm.getTenantId(), alarm.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Alarm is referencing to non-existent tenant!");
- }
- }
- }
- };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
index e92f1fd9ce..bf8d75d27a 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
@@ -22,18 +22,12 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.Cache;
-import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
-import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery;
@@ -48,17 +42,13 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
-import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.cache.EntitiesCacheManager;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -67,7 +57,6 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
-import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@@ -86,17 +75,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
private AssetDao assetDao;
@Autowired
- private TenantDao tenantDao;
+ private EntitiesCacheManager cacheManager;
@Autowired
- private CustomerDao customerDao;
-
- @Autowired
- private CacheManager cacheManager;
-
- @Autowired
- @Lazy
- private TbTenantProfileCache tenantProfileCache;
+ private DataValidator assetValidator;
@Override
public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) {
@@ -178,16 +160,11 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
}
- removeAssetFromCacheByName(asset.getTenantId(), asset.getName());
+ cacheManager.removeAssetFromCacheByName(asset.getTenantId(), asset.getName());
assetDao.removeById(tenantId, assetId.getId());
}
- private void removeAssetFromCacheByName(TenantId tenantId, String name) {
- Cache cache = cacheManager.getCache(ASSET_CACHE);
- cache.evict(Arrays.asList(tenantId, name));
- }
-
@Override
public PageData findAssetsByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
@@ -381,60 +358,6 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
return assetDao.findAssetsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
}
- private DataValidator assetValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, Asset asset) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- if (!TB_SERVICE_QUEUE.equals(asset.getType())) {
- long maxAssets = profileConfiguration.getMaxAssets();
- validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
- }
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, Asset asset) {
- Asset old = assetDao.findById(asset.getTenantId(), asset.getId().getId());
- if (old == null) {
- throw new DataValidationException("Can't update non existing asset!");
- }
- if (!old.getName().equals(asset.getName())) {
- removeAssetFromCacheByName(tenantId, old.getName());
- }
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, Asset asset) {
- if (StringUtils.isEmpty(asset.getType())) {
- throw new DataValidationException("Asset type should be specified!");
- }
- if (StringUtils.isEmpty(asset.getName())) {
- throw new DataValidationException("Asset name should be specified!");
- }
- if (asset.getTenantId() == null) {
- throw new DataValidationException("Asset should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(tenantId, asset.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Asset is referencing to non-existent tenant!");
- }
- }
- if (asset.getCustomerId() == null) {
- asset.setCustomerId(new CustomerId(NULL_UUID));
- } else if (!asset.getCustomerId().getId().equals(NULL_UUID)) {
- Customer customer = customerDao.findById(tenantId, asset.getCustomerId().getId());
- if (customer == null) {
- throw new DataValidationException("Can't assign asset to non-existent customer!");
- }
- if (!customer.getTenantId().equals(asset.getTenantId())) {
- throw new DataValidationException("Can't assign asset to customer from different tenant!");
- }
- }
- }
- };
-
private PaginatedRemover tenantAssetsRemover =
new PaginatedRemover() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
index bfd591ffa4..6e26f4343d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
@@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionStatus;
@@ -47,9 +48,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.audit.sink.AuditLogSink;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.entity.EntityService;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
-import org.thingsboard.common.util.JacksonUtil;
import java.io.PrintWriter;
import java.io.StringWriter;
@@ -80,6 +79,9 @@ public class AuditLogServiceImpl implements AuditLogService {
@Autowired
private AuditLogSink auditLogSink;
+ @Autowired
+ private DataValidator auditLogValidator;
+
@Override
public PageData findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List actionTypes, TimePageLink pageLink) {
log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink);
@@ -389,19 +391,4 @@ public class AuditLogServiceImpl implements AuditLogService {
return Futures.allAsList(futures);
}
- private DataValidator auditLogValidator =
- new DataValidator() {
- @Override
- protected void validateDataImpl(TenantId tenantId, AuditLog auditLog) {
- if (auditLog.getEntityId() == null) {
- throw new DataValidationException("Entity Id should be specified!");
- }
- if (auditLog.getTenantId() == null) {
- throw new DataValidationException("Tenant Id should be specified!");
- }
- if (auditLog.getUserId() == null) {
- throw new DataValidationException("User Id should be specified!");
- }
- }
- };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java
new file mode 100644
index 0000000000..19842eacc4
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.cache;
+
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+
+public interface EntitiesCacheManager {
+
+ void removeDeviceFromCacheByName(TenantId tenantId, String name);
+
+ void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId);
+
+ void removeAssetFromCacheByName(TenantId tenantId, String name);
+
+ void removeEdgeFromCacheByName(TenantId tenantId, String name);
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java
new file mode 100644
index 0000000000..2a28f07424
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.cache;
+
+import lombok.AllArgsConstructor;
+import org.springframework.cache.Cache;
+import org.springframework.cache.CacheManager;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.Arrays;
+
+import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE;
+import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE;
+import static org.thingsboard.server.common.data.CacheConstants.EDGE_CACHE;
+
+@Component
+@AllArgsConstructor
+public class EntitiesCacheManagerImpl implements EntitiesCacheManager {
+
+ private final CacheManager cacheManager;
+
+ @Override
+ public void removeDeviceFromCacheByName(TenantId tenantId, String name) {
+ Cache cache = cacheManager.getCache(DEVICE_CACHE);
+ cache.evict(Arrays.asList(tenantId, name));
+ }
+
+ @Override
+ public void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) {
+ if (deviceId == null) {
+ return;
+ }
+ Cache cache = cacheManager.getCache(DEVICE_CACHE);
+ cache.evict(Arrays.asList(tenantId, deviceId));
+ }
+
+ @Override
+ public void removeAssetFromCacheByName(TenantId tenantId, String name) {
+ Cache cache = cacheManager.getCache(ASSET_CACHE);
+ cache.evict(Arrays.asList(tenantId, name));
+ }
+
+ @Override
+ public void removeEdgeFromCacheByName(TenantId tenantId, String name) {
+ Cache cache = cacheManager.getCache(EDGE_CACHE);
+ cache.evict(Arrays.asList(tenantId, name));
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java
index 845665f0c4..3f6ee3cbb0 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java
@@ -21,11 +21,9 @@ import com.github.fge.jsonschema.core.report.ProcessingReport;
import com.github.fge.jsonschema.main.JsonSchemaFactory;
import com.github.fge.jsonschema.main.JsonValidator;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.ComponentDescriptorId;
-import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@@ -49,6 +47,9 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic
@Autowired
private ComponentDescriptorDao componentDescriptorDao;
+ @Autowired
+ private DataValidator componentValidator;
+
@Override
public ComponentDescriptor saveComponent(TenantId tenantId, ComponentDescriptor component) {
componentValidator.validate(component, data -> TenantId.SYS_TENANT_ID);
@@ -100,23 +101,4 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic
throw new IncorrectParameterException(e.getMessage(), e);
}
}
-
- private DataValidator componentValidator =
- new DataValidator() {
- @Override
- protected void validateDataImpl(TenantId tenantId, ComponentDescriptor plugin) {
- if (plugin.getType() == null) {
- throw new DataValidationException("Component type should be specified!");
- }
- if (plugin.getScope() == null) {
- throw new DataValidationException("Component scope should be specified!");
- }
- if (StringUtils.isEmpty(plugin.getName())) {
- throw new DataValidationException("Component name should be specified!");
- }
- if (StringUtils.isEmpty(plugin.getClazz())) {
- throw new DataValidationException("Component clazz should be specified!");
- }
- }
- };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
index 1b21569d54..7f247f5ba2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
@@ -19,30 +19,22 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.EntityType;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.dao.user.UserService;
@@ -55,7 +47,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j
public class CustomerServiceImpl extends AbstractEntityService implements CustomerService {
- private static final String PUBLIC_CUSTOMER_TITLE = "Public";
+ public static final String PUBLIC_CUSTOMER_TITLE = "Public";
public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@@ -65,9 +57,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
@Autowired
private UserService userService;
- @Autowired
- private TenantDao tenantDao;
-
@Autowired
private AssetService assetService;
@@ -84,8 +73,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
private ApiUsageStateService apiUsageStateService;
@Autowired
- @Lazy
- private TbTenantProfileCache tenantProfileCache;
+ private DataValidator customerValidator;
@Override
public Customer findCustomerById(TenantId tenantId, CustomerId customerId) {
@@ -171,56 +159,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
customersByTenantRemover.removeEntities(tenantId, tenantId);
}
- private DataValidator customerValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, Customer customer) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxCustomers = profileConfiguration.getMaxCustomers();
-
- validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
- customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
- c -> {
- throw new DataValidationException("Customer with such title already exists!");
- }
- );
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, Customer customer) {
- customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
- c -> {
- if (!c.getId().equals(customer.getId())) {
- throw new DataValidationException("Customer with such title already exists!");
- }
- }
- );
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, Customer customer) {
- if (StringUtils.isEmpty(customer.getTitle())) {
- throw new DataValidationException("Customer title should be specified!");
- }
- if (customer.getTitle().equals(PUBLIC_CUSTOMER_TITLE)) {
- throw new DataValidationException("'Public' title for customer is system reserved!");
- }
- if (!StringUtils.isEmpty(customer.getEmail())) {
- validateEmail(customer.getEmail());
- }
- if (customer.getTenantId() == null) {
- throw new DataValidationException("Customer should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(tenantId, customer.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Customer is referencing to non-existent tenant!");
- }
- }
- }
- };
-
private PaginatedRemover customersByTenantRemover =
new PaginatedRemover() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
index f89c1b0450..ee5c44e20f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
@@ -17,16 +17,12 @@ package org.thingsboard.server.dao.dashboard;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo;
-import org.thingsboard.server.common.data.EntityType;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
@@ -36,7 +32,6 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
@@ -44,8 +39,6 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import static org.thingsboard.server.dao.service.Validator.validateId;
@@ -61,9 +54,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired
private DashboardInfoDao dashboardInfoDao;
- @Autowired
- private TenantDao tenantDao;
-
@Autowired
private CustomerDao customerDao;
@@ -71,8 +61,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
private EdgeDao edgeDao;
@Autowired
- @Lazy
- private TbTenantProfileCache tenantProfileCache;
+ private DataValidator dashboardValidator;
@Override
public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
@@ -290,32 +279,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name);
}
- private DataValidator dashboardValidator =
- new DataValidator() {
- @Override
- protected void validateCreate(TenantId tenantId, Dashboard data) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxDashboards = profileConfiguration.getMaxDashboards();
- validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) {
- if (StringUtils.isEmpty(dashboard.getTitle())) {
- throw new DataValidationException("Dashboard title should be specified!");
- }
- if (dashboard.getTenantId() == null) {
- throw new DataValidationException("Dashboard should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(tenantId, dashboard.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
- }
- }
- }
- };
-
private PaginatedRemover tenantDashboardsRemover =
new PaginatedRemover() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
index 1be93dd7b9..ff92c330e0 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
@@ -25,21 +25,19 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
-import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredential;
-import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential;
-import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKBootstrapClientCredential;
-import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509BootstrapClientCredential;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.common.data.id.DeviceId;
-import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.EncryptionUtil;
@@ -47,6 +45,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.dao.service.DataValidator;
+
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString;
@@ -59,7 +58,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
private DeviceCredentialsDao deviceCredentialsDao;
@Autowired
- private DeviceService deviceService;
+ private DataValidator credentialsValidator;
@Override
public DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId) {
@@ -375,46 +374,4 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
deviceCredentialsDao.removeById(tenantId, deviceCredentials.getUuidId());
}
- private DataValidator credentialsValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, DeviceCredentials deviceCredentials) {
- if (deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId()) != null) {
- throw new DeviceCredentialsValidationException("Credentials for this device are already specified!");
- }
- if (deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()) != null) {
- throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!");
- }
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) {
- if (deviceCredentialsDao.findById(tenantId, deviceCredentials.getUuidId()) == null) {
- throw new DeviceCredentialsValidationException("Unable to update non-existent device credentials!");
- }
- DeviceCredentials existingCredentials = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId());
- if (existingCredentials != null && !existingCredentials.getId().equals(deviceCredentials.getId())) {
- throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!");
- }
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, DeviceCredentials deviceCredentials) {
- if (deviceCredentials.getDeviceId() == null) {
- throw new DeviceCredentialsValidationException("Device credentials should be assigned to device!");
- }
- if (deviceCredentials.getCredentialsType() == null) {
- throw new DeviceCredentialsValidationException("Device credentials type should be specified!");
- }
- if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) {
- throw new DeviceCredentialsValidationException("Device credentials id should be specified!");
- }
- Device device = deviceService.findDeviceById(tenantId, deviceCredentials.getDeviceId());
- if (device == null) {
- throw new DeviceCredentialsValidationException("Can't assign device credentials to non-existent device!");
- }
- }
- };
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
index 9a272b4739..602128818b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
@@ -15,83 +15,37 @@
*/
package org.thingsboard.server.dao.device;
-import com.google.protobuf.Descriptors;
-import com.google.protobuf.DynamicMessage;
-import com.squareup.wire.Syntax;
-import com.squareup.wire.schema.Field;
-import com.squareup.wire.schema.Location;
-import com.squareup.wire.schema.internal.parser.EnumElement;
-import com.squareup.wire.schema.internal.parser.FieldElement;
-import com.squareup.wire.schema.internal.parser.MessageElement;
-import com.squareup.wire.schema.internal.parser.OneOfElement;
-import com.squareup.wire.schema.internal.parser.ProtoFileElement;
-import com.squareup.wire.schema.internal.parser.ProtoParser;
-import com.squareup.wire.schema.internal.parser.TypeElement;
import lombok.extern.slf4j.Slf4j;
-import org.eclipse.leshan.core.util.SecurityUtil;
-import org.thingsboard.server.common.data.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
-import org.springframework.util.CollectionUtils;
-import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
-import org.thingsboard.server.common.data.OtaPackage;
-import org.thingsboard.server.common.data.Tenant;
-import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
-import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
-import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
-import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
-import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
-import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
-import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
-import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
-import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
-import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
-import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential;
-import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential;
-import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
-import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential;
-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;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.rule.RuleChain;
-import org.thingsboard.server.common.msg.EncryptionUtil;
-import org.thingsboard.server.common.msg.queue.ServiceType;
-import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
-import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
-import org.thingsboard.server.dao.ota.OtaPackageService;
-import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TenantDao;
-import org.thingsboard.server.queue.QueueService;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
-import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_PROFILE_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId;
@@ -104,19 +58,6 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
private static final String INCORRECT_DEVICE_PROFILE_ID = "Incorrect deviceProfileId ";
private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName ";
- private static final Location LOCATION = new Location("", "", -1, -1);
- private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema";
- private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema";
- private static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema";
- private static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema";
-
- private static String invalidSchemaProvidedMessage(String schemaName) {
- return "[Transport Configuration] invalid " + schemaName + " provided!";
- }
-
- @Autowired(required = false)
- private QueueService queueService;
-
@Autowired
private DeviceProfileDao deviceProfileDao;
@@ -126,20 +67,11 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
@Autowired
private DeviceService deviceService;
- @Autowired
- private TenantDao tenantDao;
-
@Autowired
private CacheManager cacheManager;
@Autowired
- private OtaPackageService otaPackageService;
-
- @Autowired
- private RuleChainService ruleChainService;
-
- @Autowired
- private DashboardService dashboardService;
+ private DataValidator deviceProfileValidator;
private final Lock findOrCreateLock = new ReentrantLock();
@@ -359,426 +291,6 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
tenantDeviceProfilesRemover.removeEntities(tenantId, tenantId);
}
- private DataValidator deviceProfileValidator =
- new DataValidator<>() {
- @Override
- protected void validateDataImpl(TenantId tenantId, DeviceProfile deviceProfile) {
- if (StringUtils.isEmpty(deviceProfile.getName())) {
- throw new DataValidationException("Device profile name should be specified!");
- }
- if (deviceProfile.getType() == null) {
- throw new DataValidationException("Device profile type should be specified!");
- }
- if (deviceProfile.getTransportType() == null) {
- throw new DataValidationException("Device profile transport type should be specified!");
- }
- if (deviceProfile.getTenantId() == null) {
- throw new DataValidationException("Device profile should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(deviceProfile.getTenantId(), deviceProfile.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Device profile is referencing to non-existent tenant!");
- }
- }
- if (deviceProfile.isDefault()) {
- DeviceProfile defaultDeviceProfile = findDefaultDeviceProfile(tenantId);
- if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) {
- throw new DataValidationException("Another default device profile is present in scope of current tenant!");
- }
- }
- if (!StringUtils.isEmpty(deviceProfile.getDefaultQueueName()) && queueService != null){
- if(!queueService.getQueuesByServiceType(ServiceType.TB_RULE_ENGINE).contains(deviceProfile.getDefaultQueueName())){
- throw new DataValidationException("Device profile is referencing to non-existent queue!");
- }
- }
- if (deviceProfile.getProvisionType() == null) {
- deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
- }
- DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
- transportConfiguration.validate();
- if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) {
- MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
- if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) {
- ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration =
- (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
- validateProtoSchemas(protoTransportPayloadConfiguration);
- validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration);
- validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration);
- }
- } else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) {
- CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
- CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration();
- if (coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration) {
- DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration;
- TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration();
- if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) {
- ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
- validateProtoSchemas(protoTransportPayloadConfiguration);
- validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration);
- validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration);
- }
- }
- } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) {
- List lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap();
- if (lwM2MBootstrapServersConfigurations != null) {
- validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations,
- ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable());
- for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) {
- validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential);
- }
- }
- }
-
- List profileAlarms = deviceProfile.getProfileData().getAlarms();
-
- if (!CollectionUtils.isEmpty(profileAlarms)) {
- Set alarmTypes = new HashSet<>();
- for (DeviceProfileAlarm alarm : profileAlarms) {
- String alarmType = alarm.getAlarmType();
- if (StringUtils.isEmpty(alarmType)) {
- throw new DataValidationException("Alarm rule type should be specified!");
- }
- if (!alarmTypes.add(alarmType)) {
- throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType));
- }
- }
- }
-
- if (deviceProfile.getDefaultRuleChainId() != null) {
- RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, deviceProfile.getDefaultRuleChainId());
- if (ruleChain == null) {
- throw new DataValidationException("Can't assign non-existent rule chain!");
- }
- }
-
- if (deviceProfile.getDefaultDashboardId() != null) {
- DashboardInfo dashboard = dashboardService.findDashboardInfoById(tenantId, deviceProfile.getDefaultDashboardId());
- if (dashboard == null) {
- throw new DataValidationException("Can't assign non-existent dashboard!");
- }
- }
-
- if (deviceProfile.getFirmwareId() != null) {
- OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getFirmwareId());
- if (firmware == null) {
- throw new DataValidationException("Can't assign non-existent firmware!");
- }
- if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) {
- throw new DataValidationException("Can't assign firmware with type: " + firmware.getType());
- }
- if (firmware.getData() == null && !firmware.hasUrl()) {
- throw new DataValidationException("Can't assign firmware with empty data!");
- }
- if (!firmware.getDeviceProfileId().equals(deviceProfile.getId())) {
- throw new DataValidationException("Can't assign firmware with different deviceProfile!");
- }
- }
-
- if (deviceProfile.getSoftwareId() != null) {
- OtaPackage software = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getSoftwareId());
- if (software == null) {
- throw new DataValidationException("Can't assign non-existent software!");
- }
- if (!software.getType().equals(OtaPackageType.SOFTWARE)) {
- throw new DataValidationException("Can't assign software with type: " + software.getType());
- }
- if (software.getData() == null && !software.hasUrl()) {
- throw new DataValidationException("Can't assign software with empty data!");
- }
- if (!software.getDeviceProfileId().equals(deviceProfile.getId())) {
- throw new DataValidationException("Can't assign firmware with different deviceProfile!");
- }
- }
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, DeviceProfile deviceProfile) {
- DeviceProfile old = deviceProfileDao.findById(deviceProfile.getTenantId(), deviceProfile.getId().getId());
- if (old == null) {
- throw new DataValidationException("Can't update non existing device profile!");
- }
- boolean profileTypeChanged = !old.getType().equals(deviceProfile.getType());
- boolean transportTypeChanged = !old.getTransportType().equals(deviceProfile.getTransportType());
- if (profileTypeChanged || transportTypeChanged) {
- Long profileDeviceCount = deviceDao.countDevicesByDeviceProfileId(deviceProfile.getTenantId(), deviceProfile.getId().getId());
- if (profileDeviceCount > 0) {
- String message = null;
- if (profileTypeChanged) {
- message = "Can't change device profile type because devices referenced it!";
- } else if (transportTypeChanged) {
- message = "Can't change device profile transport type because devices referenced it!";
- }
- throw new DataValidationException(message);
- }
- }
- }
-
- private void validateProtoSchemas(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {
- try {
- validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA);
- validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA);
- validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema(), RPC_REQUEST_PROTO_SCHEMA);
- validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcResponseProtoSchema(), RPC_RESPONSE_PROTO_SCHEMA);
- } catch (Exception exception) {
- throw new DataValidationException(exception.getMessage());
- }
- }
-
- private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException {
- ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray());
- ProtoFileElement protoFileElement;
- try {
- protoFileElement = schemaParser.readProtoFile();
- } catch (Exception e) {
- throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage());
- }
- checkProtoFileSyntax(schemaName, protoFileElement);
- checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!");
- checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!");
- checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!");
- checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!");
- checkTypeElements(schemaName, protoFileElement);
- }
-
- private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) {
- if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) {
- throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() +
- " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!");
- }
- }
-
- private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) {
- if (!isEmptySettings) {
- throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage);
- }
- }
-
- private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) {
- List types = protoFileElement.getTypes();
- if (!types.isEmpty()) {
- if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) {
- throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!");
- } else {
- checkEnumElements(schemaName, getEnumElements(types));
- checkMessageElements(schemaName, getMessageTypes(types));
- }
- } else {
- throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!");
- }
- }
-
- private void checkFieldElements(String schemaName, List fieldElements) {
- if (!fieldElements.isEmpty()) {
- boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> {
- Field.Label label = fieldElement.getLabel();
- return label != null && label.equals(Field.Label.REQUIRED);
- });
- if (hasRequiredLabel) {
- throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!");
- }
- boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null);
- if (hasDefaultValue) {
- throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!");
- }
- }
- }
-
- private void checkEnumElements(String schemaName, List enumTypes) {
- if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) {
- throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!");
- }
- if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) {
- throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!");
- }
- }
-
- private void checkMessageElements(String schemaName, List messageElementsList) {
- if (!messageElementsList.isEmpty()) {
- messageElementsList.forEach(messageElement -> {
- checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(),
- " Message definition groups don't support!");
- checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(),
- " Message definition options don't support!");
- checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(),
- " Message definition extensions don't support!");
- checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(),
- " Message definition reserved elements don't support!");
- checkFieldElements(schemaName, messageElement.getFields());
- List oneOfs = messageElement.getOneOfs();
- if (!oneOfs.isEmpty()) {
- oneOfs.forEach(oneOfElement -> {
- checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(),
- " OneOf definition groups don't support!");
- checkFieldElements(schemaName, oneOfElement.getFields());
- });
- }
- List nestedTypes = messageElement.getNestedTypes();
- if (!nestedTypes.isEmpty()) {
- List nestedEnumTypes = getEnumElements(nestedTypes);
- if (!nestedEnumTypes.isEmpty()) {
- checkEnumElements(schemaName, nestedEnumTypes);
- }
- List nestedMessageTypes = getMessageTypes(nestedTypes);
- checkMessageElements(schemaName, nestedMessageTypes);
- }
- });
- }
- }
-
- private List getMessageTypes(List types) {
- return types.stream()
- .filter(typeElement -> typeElement instanceof MessageElement)
- .map(typeElement -> (MessageElement) typeElement)
- .collect(Collectors.toList());
- }
-
- private List getEnumElements(List types) {
- return types.stream()
- .filter(typeElement -> typeElement instanceof EnumElement)
- .map(typeElement -> (EnumElement) typeElement)
- .collect(Collectors.toList());
- }
-
- };
-
- private void validateTelemetryDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {
- String deviceTelemetryProtoSchema = protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema();
- Descriptors.Descriptor telemetryDynamicMessageDescriptor = protoTransportPayloadTypeConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema);
- if (telemetryDynamicMessageDescriptor == null) {
- throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Failed to get telemetryDynamicMessageDescriptor!");
- } else {
- List fields = telemetryDynamicMessageDescriptor.getFields();
- if (CollectionUtils.isEmpty(fields)) {
- throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " " + telemetryDynamicMessageDescriptor.getName() + " fields is empty!");
- } else if (fields.size() == 2) {
- Descriptors.FieldDescriptor tsFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("ts");
- Descriptors.FieldDescriptor valuesFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("values");
- if (tsFieldDescriptor != null && valuesFieldDescriptor != null) {
- if (!Descriptors.FieldDescriptor.Type.MESSAGE.equals(valuesFieldDescriptor.getType())) {
- throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'values' has invalid data type. Only message type is supported!");
- }
- if (!Descriptors.FieldDescriptor.Type.INT64.equals(tsFieldDescriptor.getType())) {
- throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid data type. Only int64 type is supported!");
- }
- if (!tsFieldDescriptor.hasOptionalKeyword()) {
- throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid label. Field 'ts' should have optional keyword!");
- }
- }
- }
- }
- }
-
- private void validateRpcRequestDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {
- DynamicMessage.Builder rpcRequestDynamicMessageBuilder = protoTransportPayloadTypeConfiguration.getRpcRequestDynamicMessageBuilder(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema());
- Descriptors.Descriptor rpcRequestDynamicMessageDescriptor = rpcRequestDynamicMessageBuilder.getDescriptorForType();
- if (rpcRequestDynamicMessageDescriptor == null) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get rpcRequestDynamicMessageDescriptor!");
- } else {
- if (CollectionUtils.isEmpty(rpcRequestDynamicMessageDescriptor.getFields()) || rpcRequestDynamicMessageDescriptor.getFields().size() != 3) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " " + rpcRequestDynamicMessageDescriptor.getName() + " message should always contains 3 fields: method, requestId and params!");
- }
- Descriptors.FieldDescriptor methodFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("method");
- if (methodFieldDescriptor == null) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: method!");
- } else {
- if (!Descriptors.FieldDescriptor.Type.STRING.equals(methodFieldDescriptor.getType())) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid data type. Only string type is supported!");
- }
- if (methodFieldDescriptor.isRepeated()) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid label!");
- }
- }
- Descriptors.FieldDescriptor requestIdFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("requestId");
- if (requestIdFieldDescriptor == null) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: requestId!");
- } else {
- if (!Descriptors.FieldDescriptor.Type.INT32.equals(requestIdFieldDescriptor.getType())) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid data type. Only int32 type is supported!");
- }
- if (requestIdFieldDescriptor.isRepeated()) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid label!");
- }
- }
- Descriptors.FieldDescriptor paramsFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("params");
- if (paramsFieldDescriptor == null) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: params!");
- } else {
- if (paramsFieldDescriptor.isRepeated()) {
- throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'params' has invalid label!");
- }
- }
- }
- }
-
- private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) {
- Set uris = new HashSet<>();
- Set shortServerIds = new HashSet<>();
- for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) {
- AbstractLwM2MBootstrapServerCredential serverConfig = (AbstractLwM2MBootstrapServerCredential) bootstrapServerCredential;
- if (!isBootstrapServerUpdateEnable && serverConfig.isBootstrapServerIs()) {
- throw new DeviceCredentialsValidationException("Bootstrap config must not include \"Bootstrap Server\". \"Include Bootstrap Server updates\" is " + isBootstrapServerUpdateEnable + ".");
- }
- String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server" + " shortServerId: " + serverConfig.getShortServerId() + ":";
- if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) {
- throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!");
- }
- if (!shortServerIds.add(serverConfig.getShortServerId())) {
- throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!");
- }
- String uri = serverConfig.getHost() + ":" + serverConfig.getPort();
- if (!uris.add(uri)) {
- throw new DeviceCredentialsValidationException(server + " \"Host + port\" value = " + uri + ". This value must be a unique value for all servers!");
- }
- Integer port;
- if (LwM2MSecurityMode.NO_SEC.equals(serverConfig.getSecurityMode())) {
- port = serverConfig.isBootstrapServerIs() ? 5687 : 5685;
- } else {
- port = serverConfig.isBootstrapServerIs() ? 5688 : 5686;
- }
- if (serverConfig.getPort() == null || serverConfig.getPort().intValue() != port) {
- throw new DeviceCredentialsValidationException(server + " \"Port\" value = " + serverConfig.getPort() + ". This value for security " + serverConfig.getSecurityMode().name() + " must be " + port + "!");
- }
- }
- }
-
- private void validateLwm2mServersCredentialOfBootstrapForClient(LwM2MBootstrapServerCredential bootstrapServerConfig) {
- String server;
- switch (bootstrapServerConfig.getSecurityMode()) {
- case NO_SEC:
- case PSK:
- break;
- case RPK:
- RPKLwM2MBootstrapServerCredential rpkServerCredentials = (RPKLwM2MBootstrapServerCredential) bootstrapServerConfig;
- server = rpkServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server";
- if (StringUtils.isEmpty(rpkServerCredentials.getServerPublicKey())) {
- throw new DeviceCredentialsValidationException(server + " RPK public key must be specified!");
- }
- try {
- String pubkRpkSever = EncryptionUtil.pubkTrimNewLines(rpkServerCredentials.getServerPublicKey());
- rpkServerCredentials.setServerPublicKey(pubkRpkSever);
- SecurityUtil.publicKey.decode(rpkServerCredentials.getDecodedCServerPublicKey());
- } catch (Exception e) {
- throw new DeviceCredentialsValidationException(server + " RPK public key must be in standard [RFC7250] and then encoded to Base64 format!");
- }
- break;
- case X509:
- X509LwM2MBootstrapServerCredential x509ServerCredentials = (X509LwM2MBootstrapServerCredential) bootstrapServerConfig;
- server = x509ServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server";
- if (StringUtils.isEmpty(x509ServerCredentials.getServerPublicKey())) {
- throw new DeviceCredentialsValidationException(server + " X509 certificate must be specified!");
- }
-
- try {
- String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getServerPublicKey());
- x509ServerCredentials.setServerPublicKey(certServer);
- SecurityUtil.certificate.decode(x509ServerCredentials.getDecodedCServerPublicKey());
- } catch (Exception e) {
- throw new DeviceCredentialsValidationException(server + " X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!");
- }
- break;
- }
- }
-
private PaginatedRemover tenantDeviceProfilesRemover =
new PaginatedRemover() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
index b2a6c10337..a3260ffa17 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
@@ -23,18 +23,14 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.Cache;
-import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile;
@@ -42,15 +38,12 @@ import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
-import org.thingsboard.server.common.data.OtaPackage;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.DeviceSearchQuery;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData;
-import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration;
@@ -69,23 +62,18 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
-import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.cache.EntitiesCacheManager;
import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.exception.DataValidationException;
-import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -96,7 +84,6 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
-import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@@ -116,12 +103,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Autowired
private DeviceDao deviceDao;
- @Autowired
- private TenantDao tenantDao;
-
- @Autowired
- private CustomerDao customerDao;
-
@Autowired
private DeviceCredentialsService deviceCredentialsService;
@@ -129,17 +110,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
private DeviceProfileService deviceProfileService;
@Autowired
- private CacheManager cacheManager;
+ private EntitiesCacheManager cacheManager;
@Autowired
private EventService eventService;
@Autowired
- @Lazy
- private TbTenantProfileCache tenantProfileCache;
-
- @Autowired
- private OtaPackageService otaPackageService;
+ private DataValidator deviceValidator;
@Override
public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
@@ -274,8 +251,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) {
// remove device from cache in case null value cached in the distributed redis.
- removeDeviceFromCacheByName(device.getTenantId(), device.getName());
- removeDeviceFromCacheById(device.getTenantId(), device.getId());
+ cacheManager.removeDeviceFromCacheByName(device.getTenantId(), device.getName());
+ cacheManager.removeDeviceFromCacheById(device.getTenantId(), device.getId());
throw new DataValidationException("Device with such name already exists!");
} else {
throw t;
@@ -321,8 +298,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
Device device = findDeviceById(tenantId, deviceId);
device.setCustomerId(customerId);
Device savedDevice = saveDevice(device);
- removeDeviceFromCacheByName(tenantId, device.getName());
- removeDeviceFromCacheById(tenantId, device.getId());
+ cacheManager.removeDeviceFromCacheByName(tenantId, device.getName());
+ cacheManager.removeDeviceFromCacheById(tenantId, device.getId());
return savedDevice;
}
@@ -331,8 +308,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
Device device = findDeviceById(tenantId, deviceId);
device.setCustomerId(null);
Device savedDevice = saveDevice(device);
- removeDeviceFromCacheByName(tenantId, device.getName());
- removeDeviceFromCacheById(tenantId, device.getId());
+ cacheManager.removeDeviceFromCacheByName(tenantId, device.getName());
+ cacheManager.removeDeviceFromCacheById(tenantId, device.getId());
return savedDevice;
}
@@ -362,22 +339,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
deviceDao.removeById(tenantId, deviceId.getId());
- removeDeviceFromCacheByName(tenantId, deviceName);
- removeDeviceFromCacheById(tenantId, deviceId);
+ cacheManager.removeDeviceFromCacheByName(tenantId, deviceName);
+ cacheManager.removeDeviceFromCacheById(tenantId, deviceId);
}
- private void removeDeviceFromCacheByName(TenantId tenantId, String name) {
- Cache cache = cacheManager.getCache(DEVICE_CACHE);
- cache.evict(Arrays.asList(tenantId, name));
- }
- private void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) {
- if (deviceId == null) {
- return;
- }
- Cache cache = cacheManager.getCache(DEVICE_CACHE);
- cache.evict(Arrays.asList(tenantId, deviceId));
- }
@Override
public PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink) {
@@ -590,8 +556,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
// explicitly remove device with previous tenant id from cache
// result device object will have different tenant id and will not remove entity from cache
- removeDeviceFromCacheByName(oldTenantId, device.getName());
- removeDeviceFromCacheById(oldTenantId, device.getId());
+ cacheManager.removeDeviceFromCacheByName(oldTenantId, device.getName());
+ cacheManager.removeDeviceFromCacheById(oldTenantId, device.getId());
return savedDevice;
}
@@ -639,7 +605,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name());
}
}
- removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); // eviction by name is described as annotation @CacheEvict above
+ cacheManager.removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); // eviction by name is described as annotation @CacheEvict above
return savedDevice;
}
@@ -710,91 +676,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
return deviceDao.countByTenantId(tenantId);
}
- private DataValidator deviceValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, Device device) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxDevices = profileConfiguration.getMaxDevices();
- validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, Device device) {
- Device old = deviceDao.findById(device.getTenantId(), device.getId().getId());
- if (old == null) {
- throw new DataValidationException("Can't update non existing device!");
- }
- if (!old.getName().equals(device.getName())) {
- removeDeviceFromCacheByName(tenantId, old.getName());
- removeDeviceFromCacheById(tenantId, device.getId());
- }
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, Device device) {
- if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) {
- throw new DataValidationException("Device name should be specified!");
- }
- if (device.getTenantId() == null) {
- throw new DataValidationException("Device should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(device.getTenantId(), device.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Device is referencing to non-existent tenant!");
- }
- }
- if (device.getCustomerId() == null) {
- device.setCustomerId(new CustomerId(NULL_UUID));
- } else if (!device.getCustomerId().getId().equals(NULL_UUID)) {
- Customer customer = customerDao.findById(device.getTenantId(), device.getCustomerId().getId());
- if (customer == null) {
- throw new DataValidationException("Can't assign device to non-existent customer!");
- }
- if (!customer.getTenantId().getId().equals(device.getTenantId().getId())) {
- throw new DataValidationException("Can't assign device to customer from different tenant!");
- }
- }
- Optional.ofNullable(device.getDeviceData())
- .flatMap(deviceData -> Optional.ofNullable(deviceData.getTransportConfiguration()))
- .ifPresent(DeviceTransportConfiguration::validate);
-
- if (device.getFirmwareId() != null) {
- OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, device.getFirmwareId());
- if (firmware == null) {
- throw new DataValidationException("Can't assign non-existent firmware!");
- }
- if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) {
- throw new DataValidationException("Can't assign firmware with type: " + firmware.getType());
- }
- if (firmware.getData() == null && !firmware.hasUrl()) {
- throw new DataValidationException("Can't assign firmware with empty data!");
- }
- if (!firmware.getDeviceProfileId().equals(device.getDeviceProfileId())) {
- throw new DataValidationException("Can't assign firmware with different deviceProfile!");
- }
- }
-
- if (device.getSoftwareId() != null) {
- OtaPackage software = otaPackageService.findOtaPackageById(tenantId, device.getSoftwareId());
- if (software == null) {
- throw new DataValidationException("Can't assign non-existent software!");
- }
- if (!software.getType().equals(OtaPackageType.SOFTWARE)) {
- throw new DataValidationException("Can't assign software with type: " + software.getType());
- }
- if (software.getData() == null && !software.hasUrl()) {
- throw new DataValidationException("Can't assign software with empty data!");
- }
- if (!software.getDeviceProfileId().equals(device.getDeviceProfileId())) {
- throw new DataValidationException("Can't assign firmware with different deviceProfile!");
- }
- }
- }
- };
-
private PaginatedRemover tenantDevicesRemover =
new PaginatedRemover() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java
index aa96850a67..9d18146ec2 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java
@@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
@Service
@@ -33,6 +32,9 @@ public class BaseEdgeEventService implements EdgeEventService {
@Autowired
private EdgeEventDao edgeEventDao;
+ @Autowired
+ private DataValidator edgeEventValidator;
+
@Override
public EdgeEvent save(EdgeEvent edgeEvent) {
edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId);
@@ -48,17 +50,4 @@ public class BaseEdgeEventService implements EdgeEventService {
public void cleanupEvents(long ttl) {
edgeEventDao.cleanupEvents(ttl);
}
-
- private DataValidator edgeEventValidator =
- new DataValidator() {
- @Override
- protected void validateDataImpl(TenantId tenantId, EdgeEvent edgeEvent) {
- if (edgeEvent.getEdgeId() == null) {
- throw new DataValidationException("Edge id should be specified!");
- }
- if (edgeEvent.getAction() == null) {
- throw new DataValidationException("Edge Event action should be specified!");
- }
- }
- };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
index 3cdbcf7bc9..80d3a1bb03 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
@@ -25,16 +25,11 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.cache.Cache;
-import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
-import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeInfo;
@@ -52,7 +47,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
-import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.cache.EntitiesCacheManager;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService;
@@ -60,12 +55,10 @@ import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.user.UserService;
import javax.annotation.Nullable;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -74,7 +67,6 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.EDGE_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
-import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@@ -95,17 +87,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Autowired
private EdgeDao edgeDao;
- @Autowired
- private TenantDao tenantDao;
-
- @Autowired
- private CustomerDao customerDao;
-
@Autowired
private UserService userService;
@Autowired
- private CacheManager cacheManager;
+ private EntitiesCacheManager cacheManager;
@Autowired
private RuleChainService ruleChainService;
@@ -113,6 +99,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Autowired
private RelationService relationService;
+ @Autowired
+ private DataValidator edgeValidator;
+
@Override
public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) {
log.trace("Executing findEdgeById [{}]", edgeId);
@@ -152,11 +141,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@CacheEvict(cacheNames = EDGE_CACHE, key = "{#edge.tenantId, #edge.name}")
@Override
- public Edge saveEdge(Edge edge, boolean doValidate) {
+ public Edge saveEdge(Edge edge) {
log.trace("Executing saveEdge [{}]", edge);
- if (doValidate) {
- edgeValidator.validate(edge, Edge::getTenantId);
- }
+ edgeValidator.validate(edge, Edge::getTenantId);
try {
return edgeDao.save(edge.getTenantId(), edge);
} catch (Exception t) {
@@ -175,7 +162,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
log.trace("[{}] Executing assignEdgeToCustomer [{}][{}]", tenantId, edgeId, customerId);
Edge edge = findEdgeById(tenantId, edgeId);
edge.setCustomerId(customerId);
- return saveEdge(edge, true);
+ return saveEdge(edge);
}
@Override
@@ -183,7 +170,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
log.trace("[{}] Executing unassignEdgeFromCustomer [{}]", tenantId, edgeId);
Edge edge = findEdgeById(tenantId, edgeId);
edge.setCustomerId(null);
- return saveEdge(edge, true);
+ return saveEdge(edge);
}
@Override
@@ -195,16 +182,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
deleteEntityRelations(tenantId, edgeId);
- removeEdgeFromCacheByName(edge.getTenantId(), edge.getName());
+ cacheManager.removeEdgeFromCacheByName(edge.getTenantId(), edge.getName());
edgeDao.removeById(tenantId, edgeId.getId());
}
- private void removeEdgeFromCacheByName(TenantId tenantId, String name) {
- Cache cache = cacheManager.getCache(EDGE_CACHE);
- cache.evict(Arrays.asList(tenantId, name));
- }
-
@Override
public PageData findEdgesByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findEdgesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
@@ -330,7 +312,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Nullable
@Override
public List apply(@Nullable List edgeList) {
- return edgeList == null ? Collections.emptyList() : edgeList.stream().filter(edge -> query.getEdgeTypes().contains(edge.getType())).collect(Collectors.toList());
+ return edgeList == null ?
+ Collections.emptyList() :
+ edgeList.stream().filter(edge -> query.getEdgeTypes().contains(edge.getType())).collect(Collectors.toList());
}
}, MoreExecutors.directExecutor());
@@ -375,57 +359,6 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
return edgeDao.findEdgesByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), entityId.getEntityType(), pageLink);
}
- private DataValidator edgeValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, Edge edge) {
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, Edge edge) {
- Edge old = edgeDao.findById(edge.getTenantId(), edge.getId().getId());
- if (!old.getName().equals(edge.getName())) {
- removeEdgeFromCacheByName(tenantId, old.getName());
- }
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, Edge edge) {
- if (StringUtils.isEmpty(edge.getType())) {
- throw new DataValidationException("Edge type should be specified!");
- }
- if (StringUtils.isEmpty(edge.getName())) {
- throw new DataValidationException("Edge name should be specified!");
- }
- if (StringUtils.isEmpty(edge.getSecret())) {
- throw new DataValidationException("Edge secret should be specified!");
- }
- if (StringUtils.isEmpty(edge.getRoutingKey())) {
- throw new DataValidationException("Edge routing key should be specified!");
- }
- if (edge.getTenantId() == null) {
- throw new DataValidationException("Edge should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(edge.getTenantId(), edge.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Edge is referencing to non-existent tenant!");
- }
- }
- if (edge.getCustomerId() == null) {
- edge.setCustomerId(new CustomerId(NULL_UUID));
- } else if (!edge.getCustomerId().getId().equals(NULL_UUID)) {
- Customer customer = customerDao.findById(edge.getTenantId(), edge.getCustomerId().getId());
- if (customer == null) {
- throw new DataValidationException("Can't assign edge to non-existent customer!");
- }
- if (!customer.getTenantId().getId().equals(edge.getTenantId().getId())) {
- throw new DataValidationException("Can't assign edge to customer from different tenant!");
- }
- }
- }
- };
-
private PaginatedRemover tenantEdgesRemover =
new PaginatedRemover() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
index 624c5e5ef7..e724980123 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
@@ -21,7 +21,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
@@ -29,12 +28,10 @@ import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
@@ -47,12 +44,10 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
-import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
-import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
@@ -65,7 +60,6 @@ import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE;
-import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString;
@@ -87,13 +81,10 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
private EntityViewDao entityViewDao;
@Autowired
- private TenantDao tenantDao;
-
- @Autowired
- private CustomerDao customerDao;
+ private CacheManager cacheManager;
@Autowired
- private CacheManager cacheManager;
+ private DataValidator entityViewValidator;
@Caching(evict = {
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"),
@@ -103,8 +94,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
public EntityView saveEntityView(EntityView entityView) {
log.trace("Executing save entity view [{}]", entityView);
entityViewValidator.validate(entityView, EntityView::getTenantId);
- EntityView savedEntityView = entityViewDao.save(entityView.getTenantId(), entityView);
- return savedEntityView;
+ return entityViewDao.save(entityView.getTenantId(), entityView);
}
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}")
@@ -400,57 +390,6 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
return entityViewDao.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
}
- private DataValidator entityViewValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, EntityView entityView) {
- entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
- .ifPresent(e -> {
- throw new DataValidationException("Entity view with such name already exists!");
- });
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, EntityView entityView) {
- entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
- .ifPresent(e -> {
- if (!e.getUuidId().equals(entityView.getUuidId())) {
- throw new DataValidationException("Entity view with such name already exists!");
- }
- });
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, EntityView entityView) {
- if (StringUtils.isEmpty(entityView.getType())) {
- throw new DataValidationException("Entity View type should be specified!");
- }
- if (StringUtils.isEmpty(entityView.getName())) {
- throw new DataValidationException("Entity view name should be specified!");
- }
- if (entityView.getTenantId() == null) {
- throw new DataValidationException("Entity view should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(tenantId, entityView.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Entity view is referencing to non-existent tenant!");
- }
- }
- if (entityView.getCustomerId() == null) {
- entityView.setCustomerId(new CustomerId(NULL_UUID));
- } else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) {
- Customer customer = customerDao.findById(tenantId, entityView.getCustomerId().getId());
- if (customer == null) {
- throw new DataValidationException("Can't assign entity view to non-existent customer!");
- }
- if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) {
- throw new DataValidationException("Can't assign entity view to customer from different tenant!");
- }
- }
- }
- };
-
private PaginatedRemover tenantEntityViewRemover = new PaginatedRemover() {
@Override
protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
index 714eefd0b1..3937bbd9c3 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
@@ -46,6 +46,9 @@ public class BaseEventService implements EventService {
@Autowired
public EventDao eventDao;
+ @Autowired
+ private DataValidator eventValidator;
+
@Override
public ListenableFuture saveAsync(Event event) {
eventValidator.validate(event, Event::getTenantId);
@@ -128,20 +131,4 @@ public class BaseEventService implements EventService {
public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) {
eventDao.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
}
-
- private DataValidator eventValidator =
- new DataValidator() {
- @Override
- protected void validateDataImpl(TenantId tenantId, Event event) {
- if (event.getEntityId() == null) {
- throw new DataValidationException("Entity id should be specified!.");
- }
- if (StringUtils.isEmpty(event.getType())) {
- throw new DataValidationException("Event type should be specified!.");
- }
- if (event.getBody() == null) {
- throw new DataValidationException("Event body should be specified!.");
- }
- }
- };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java
index a3d6099c5d..d35d7e241c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java
@@ -15,15 +15,13 @@
*/
package org.thingsboard.server.dao.oauth2;
+import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import org.springframework.util.StringUtils;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.oauth2.*;
+import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
@@ -36,12 +34,14 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
@Slf4j
@Service
+@AllArgsConstructor
public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService {
- public static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId ";
- public static final String INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID = "Incorrect clientRegistrationProviderId ";
- @Autowired
- private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao;
+ private static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId ";
+ private static final String INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID = "Incorrect clientRegistrationProviderId ";
+
+ private final OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao;
+ private final DataValidator clientRegistrationTemplateValidator;
@Override
public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
@@ -87,32 +87,4 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple
validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId);
clientRegistrationTemplateDao.removeById(TenantId.SYS_TENANT_ID, templateId.getId());
}
-
- private final DataValidator clientRegistrationTemplateValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
- if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) {
- throw new DataValidationException("Provider ID should be specified!");
- }
- if (clientRegistrationTemplate.getMapperConfig() == null) {
- throw new DataValidationException("Mapper config should be specified!");
- }
- if (clientRegistrationTemplate.getMapperConfig().getType() == null) {
- throw new DataValidationException("Mapper type should be specified!");
- }
- if (clientRegistrationTemplate.getMapperConfig().getBasic() == null) {
- throw new DataValidationException("Basic mapper config should be specified!");
- }
- }
- };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
index 838e79ad70..ecf8d68267 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
@@ -21,18 +21,14 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
-import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
-import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -40,22 +36,16 @@ import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
-import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
-import java.util.Objects;
import java.util.Optional;
import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE;
-import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@@ -66,16 +56,12 @@ public class BaseOtaPackageService implements OtaPackageService {
public static final String INCORRECT_OTA_PACKAGE_ID = "Incorrect otaPackageId ";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
- private final TenantDao tenantDao;
- private final DeviceProfileDao deviceProfileDao;
private final OtaPackageDao otaPackageDao;
private final OtaPackageInfoDao otaPackageInfoDao;
private final CacheManager cacheManager;
private final OtaPackageDataCache otaPackageDataCache;
-
- @Autowired
- @Lazy
- private TbTenantProfileCache tenantProfileCache;
+ private final DataValidator otaPackageInfoValidator;
+ private final DataValidator otaPackageValidator;
@Override
public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl) {
@@ -230,168 +216,6 @@ public class BaseOtaPackageService implements OtaPackageService {
tenantOtaPackageRemover.removeEntities(tenantId, tenantId);
}
- private DataValidator otaPackageInfoValidator = new DataValidator<>() {
-
- @Override
- protected void validateDataImpl(TenantId tenantId, OtaPackageInfo otaPackageInfo) {
- validateImpl(otaPackageInfo);
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, OtaPackageInfo otaPackage) {
- OtaPackageInfo otaPackageOld = otaPackageInfoDao.findById(tenantId, otaPackage.getUuidId());
- BaseOtaPackageService.validateUpdate(otaPackage, otaPackageOld);
- }
- };
-
- private DataValidator otaPackageValidator = new DataValidator<>() {
-
- @Override
- protected void validateCreate(TenantId tenantId, OtaPackage otaPackage) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
- validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, OtaPackage otaPackage) {
- validateImpl(otaPackage);
-
- if (!otaPackage.hasUrl()) {
- if (StringUtils.isEmpty(otaPackage.getFileName())) {
- throw new DataValidationException("OtaPackage file name should be specified!");
- }
-
- if (StringUtils.isEmpty(otaPackage.getContentType())) {
- throw new DataValidationException("OtaPackage content type should be specified!");
- }
-
- if (otaPackage.getChecksumAlgorithm() == null) {
- throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
- }
- if (StringUtils.isEmpty(otaPackage.getChecksum())) {
- throw new DataValidationException("OtaPackage checksum should be specified!");
- }
-
- String currentChecksum;
-
- currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
-
- if (!currentChecksum.equals(otaPackage.getChecksum())) {
- throw new DataValidationException("Wrong otaPackage file!");
- }
- } else {
- if(otaPackage.getData() != null) {
- throw new DataValidationException("File can't be saved if URL present!");
- }
- }
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, OtaPackage otaPackage) {
- OtaPackage otaPackageOld = otaPackageDao.findById(tenantId, otaPackage.getUuidId());
-
- BaseOtaPackageService.validateUpdate(otaPackage, otaPackageOld);
-
- if (otaPackageOld.getData() != null && !otaPackageOld.getData().equals(otaPackage.getData())) {
- throw new DataValidationException("Updating otaPackage data is prohibited!");
- }
-
- if (otaPackageOld.getData() == null && otaPackage.getData() != null) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
- validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
- }
- }
- };
-
- private static void validateUpdate(OtaPackageInfo otaPackage, OtaPackageInfo otaPackageOld) {
- if (!otaPackageOld.getType().equals(otaPackage.getType())) {
- throw new DataValidationException("Updating type is prohibited!");
- }
-
- if (!otaPackageOld.getTitle().equals(otaPackage.getTitle())) {
- throw new DataValidationException("Updating otaPackage title is prohibited!");
- }
-
- if (!otaPackageOld.getVersion().equals(otaPackage.getVersion())) {
- throw new DataValidationException("Updating otaPackage version is prohibited!");
- }
-
- if (!Objects.equals(otaPackage.getTag(), otaPackageOld.getTag())) {
- throw new DataValidationException("Updating otaPackage tag is prohibited!");
- }
-
- if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) {
- throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!");
- }
-
- if (otaPackageOld.getFileName() != null && !otaPackageOld.getFileName().equals(otaPackage.getFileName())) {
- throw new DataValidationException("Updating otaPackage file name is prohibited!");
- }
-
- if (otaPackageOld.getContentType() != null && !otaPackageOld.getContentType().equals(otaPackage.getContentType())) {
- throw new DataValidationException("Updating otaPackage content type is prohibited!");
- }
-
- if (otaPackageOld.getChecksumAlgorithm() != null && !otaPackageOld.getChecksumAlgorithm().equals(otaPackage.getChecksumAlgorithm())) {
- throw new DataValidationException("Updating otaPackage content type is prohibited!");
- }
-
- if (otaPackageOld.getChecksum() != null && !otaPackageOld.getChecksum().equals(otaPackage.getChecksum())) {
- throw new DataValidationException("Updating otaPackage content type is prohibited!");
- }
-
- if (otaPackageOld.getDataSize() != null && !otaPackageOld.getDataSize().equals(otaPackage.getDataSize())) {
- throw new DataValidationException("Updating otaPackage data size is prohibited!");
- }
-
- if(otaPackageOld.getUrl() != null && !otaPackageOld.getUrl().equals(otaPackage.getUrl())) {
- throw new DataValidationException("Updating otaPackage URL is prohibited!");
- }
- }
-
- private void validateImpl(OtaPackageInfo otaPackageInfo) {
- if (otaPackageInfo.getTenantId() == null) {
- throw new DataValidationException("OtaPackage should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("OtaPackage is referencing to non-existent tenant!");
- }
- }
-
- if (otaPackageInfo.getDeviceProfileId() != null) {
- DeviceProfile deviceProfile = deviceProfileDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getDeviceProfileId().getId());
- if (deviceProfile == null) {
- throw new DataValidationException("OtaPackage is referencing to non-existent device profile!");
- }
- }
-
- if (otaPackageInfo.getType() == null) {
- throw new DataValidationException("Type should be specified!");
- }
-
- if (StringUtils.isEmpty(otaPackageInfo.getTitle())) {
- throw new DataValidationException("OtaPackage title should be specified!");
- }
-
- if (StringUtils.isEmpty(otaPackageInfo.getVersion())) {
- throw new DataValidationException("OtaPackage version should be specified!");
- }
-
- if(otaPackageInfo.getTitle().length() > 255) {
- throw new DataValidationException("The length of title should be equal or shorter than 255");
- }
-
- if(otaPackageInfo.getVersion().length() > 255) {
- throw new DataValidationException("The length of version should be equal or shorter than 255");
- }
-
- }
-
private PaginatedRemover tenantOtaPackageRemover =
new PaginatedRemover<>() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
index c515e95f73..dd13f5b200 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
@@ -16,51 +16,37 @@
package org.thingsboard.server.dao.resource;
import com.google.common.util.concurrent.ListenableFuture;
+import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.exception.DataValidationException;
-import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.List;
import java.util.Optional;
-import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
+@AllArgsConstructor
public class BaseResourceService implements ResourceService {
public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId ";
private final TbResourceDao resourceDao;
private final TbResourceInfoDao resourceInfoDao;
- private final TenantDao tenantDao;
- private final TbTenantProfileCache tenantProfileCache;
-
- public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao, @Lazy TbTenantProfileCache tenantProfileCache) {
- this.resourceDao = resourceDao;
- this.resourceInfoDao = resourceInfoDao;
- this.tenantDao = tenantDao;
- this.tenantProfileCache = tenantProfileCache;
- }
+ private final DataValidator resourceValidator;
@Override
public TbResource saveResource(TbResource resource) {
@@ -153,45 +139,7 @@ public class BaseResourceService implements ResourceService {
return resourceDao.sumDataSizeByTenantId(tenantId);
}
- private DataValidator resourceValidator = new DataValidator<>() {
-
- @Override
- protected void validateCreate(TenantId tenantId, TbResource resource) {
- if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId) ) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes();
- validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length(), TB_RESOURCE);
- }
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, TbResource resource) {
- if (StringUtils.isEmpty(resource.getTitle())) {
- throw new DataValidationException("Resource title should be specified!");
- }
- if (resource.getResourceType() == null) {
- throw new DataValidationException("Resource type should be specified!");
- }
- if (StringUtils.isEmpty(resource.getFileName())) {
- throw new DataValidationException("Resource file name should be specified!");
- }
- if (StringUtils.isEmpty(resource.getResourceKey())) {
- throw new DataValidationException("Resource key should be specified!");
- }
- if (resource.getTenantId() == null) {
- resource.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
- }
- if (!resource.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
- Tenant tenant = tenantDao.findById(tenantId, resource.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Resource is referencing to non-existent tenant!");
- }
- }
- }
- };
-
- private PaginatedRemover tenantResourcesRemover =
+ private final PaginatedRemover tenantResourcesRemover =
new PaginatedRemover<>() {
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
index b522cb8bf0..097779f33e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
@@ -21,16 +21,14 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
-import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
@@ -51,15 +49,12 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList;
import java.util.Collection;
@@ -94,11 +89,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
private RuleNodeDao ruleNodeDao;
@Autowired
- private TenantDao tenantDao;
-
- @Autowired
- @Lazy
- private TbTenantProfileCache tenantProfileCache;
+ private DataValidator ruleChainValidator;
@Override
@Transactional
@@ -209,11 +200,37 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
if (ruleChainMetaData.getRuleChainConnections() != null) {
for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) {
+ RuleChainId targetRuleChainId = nodeToRuleChainConnection.getTargetRuleChainId();
+ RuleChain targetRuleChain = findRuleChainById(TenantId.SYS_TENANT_ID, targetRuleChainId);
+ RuleNode targetNode = new RuleNode();
+ targetNode.setName(targetRuleChain != null ? targetRuleChain.getName() : "Rule Chain Input");
+ targetNode.setRuleChainId(ruleChain.getId());
+ targetNode.setType("org.thingsboard.rule.engine.flow.TbRuleChainInputNode");
+ var configuration = JacksonUtil.newObjectNode();
+ configuration.put("ruleChainId", targetRuleChainId.getId().toString());
+ targetNode.setConfiguration(configuration);
+ ObjectNode layout = (ObjectNode) nodeToRuleChainConnection.getAdditionalInfo();
+ layout.remove("description");
+ layout.remove("ruleChainNodeId");
+ targetNode.setAdditionalInfo(layout);
+ targetNode.setDebugMode(false);
+ targetNode = ruleNodeDao.save(tenantId, targetNode);
+
+ EntityRelation sourceRuleChainToRuleNode = new EntityRelation();
+ sourceRuleChainToRuleNode.setFrom(ruleChain.getId());
+ sourceRuleChainToRuleNode.setTo(targetNode.getId());
+ sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE);
+ sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN);
+ relationService.saveRelation(tenantId, sourceRuleChainToRuleNode);
+
+ EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation();
EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId();
- EntityId to = nodeToRuleChainConnection.getTargetRuleChainId();
- String type = nodeToRuleChainConnection.getType();
- createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo()));
- }
+ sourceRuleNodeToTargetRuleNode.setFrom(from);
+ sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId());
+ sourceRuleNodeToTargetRuleNode.setType(nodeToRuleChainConnection.getType());
+ sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE);
+ relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode);
+ }
}
}
@@ -273,8 +290,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
int toIndex = ruleNodeIndexMap.get(toNodeId);
ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type);
} else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) {
- RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId());
- ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type, nodeRelation.getAdditionalInfo());
+ log.warn("[{}][{}] Unsupported node relation: {}", tenantId, ruleChainId, nodeRelation.getTo());
}
}
}
@@ -519,23 +535,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
}
}
- private List findAllTenantRuleChains(TenantId tenantId, RuleChainType type) {
- PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
- return findAllTenantRuleChainsRecursive(tenantId, new ArrayList<>(), type, pageLink);
- }
-
- private List findAllTenantRuleChainsRecursive(TenantId tenantId, List accumulator, RuleChainType type, PageLink pageLink) {
- PageData persistentRuleChainData = findTenantRuleChainsByType(tenantId, type, pageLink);
- List ruleChains = persistentRuleChainData.getData();
- if (!CollectionUtils.isEmpty(ruleChains)) {
- accumulator.addAll(ruleChains);
- }
- if (persistentRuleChainData.hasNext()) {
- return findAllTenantRuleChainsRecursive(tenantId, accumulator, type, pageLink.nextPageLink());
- }
- return accumulator;
- }
-
private void setNewRuleChainId(RuleChain ruleChain, List metadata, RuleChainId oldRuleChainId, RuleChainId newRuleChainId) {
ruleChain.setId(newRuleChainId);
for (RuleChainMetaData metaData : metadata) {
@@ -705,46 +704,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
ruleNodeDao.removeById(tenantId, entityId.getId());
}
- private final DataValidator ruleChainValidator =
- new DataValidator<>() {
- @Override
- protected void validateCreate(TenantId tenantId, RuleChain data) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxRuleChains = profileConfiguration.getMaxRuleChains();
- validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
- if (StringUtils.isEmpty(ruleChain.getName())) {
- throw new DataValidationException("Rule chain name should be specified!");
- }
- if (ruleChain.getType() == null) {
- ruleChain.setType(RuleChainType.CORE);
- }
- if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
- throw new DataValidationException("Rule chain should be assigned to tenant!");
- }
- Tenant tenant = tenantDao.findById(tenantId, ruleChain.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
- }
- if (ruleChain.isRoot() && RuleChainType.CORE.equals(ruleChain.getType())) {
- RuleChain rootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId());
- if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) {
- throw new DataValidationException("Another root rule chain is present in scope of current tenant!");
- }
- }
- if (ruleChain.isRoot() && RuleChainType.EDGE.equals(ruleChain.getType())) {
- RuleChain edgeTemplateRootRuleChain = getEdgeTemplateRootRuleChain(ruleChain.getTenantId());
- if (edgeTemplateRootRuleChain != null && !edgeTemplateRootRuleChain.getId().equals(ruleChain.getId())) {
- throw new DataValidationException("Another edge template root rule chain is present in scope of current tenant!");
- }
- }
- }
- };
-
private final PaginatedRemover tenantRuleChainsRemover =
new PaginatedRemover<>() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java
new file mode 100644
index 0000000000..c1485cfb1a
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java
@@ -0,0 +1,61 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.AdminSettings;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.settings.AdminSettingsService;
+
+@Component
+@AllArgsConstructor
+public class AdminSettingsDataValidator extends DataValidator {
+
+ private final AdminSettingsService adminSettingsService;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, AdminSettings adminSettings) {
+ AdminSettings existentAdminSettingsWithKey = adminSettingsService.findAdminSettingsByKey(tenantId, adminSettings.getKey());
+ if (existentAdminSettingsWithKey != null) {
+ throw new DataValidationException("Admin settings with such name already exists!");
+ }
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, AdminSettings adminSettings) {
+ AdminSettings existentAdminSettings = adminSettingsService.findAdminSettingsById(tenantId, adminSettings.getId());
+ if (existentAdminSettings != null) {
+ if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) {
+ throw new DataValidationException("Changing key of admin settings entry is prohibited!");
+ }
+ }
+ }
+
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, AdminSettings adminSettings) {
+ if (StringUtils.isEmpty(adminSettings.getKey())) {
+ throw new DataValidationException("Key should be specified!");
+ }
+ if (adminSettings.getJsonValue() == null) {
+ throw new DataValidationException("Json value should be specified!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java
new file mode 100644
index 0000000000..5bbe636dc7
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+@Component
+@AllArgsConstructor
+public class AlarmDataValidator extends DataValidator {
+
+ private final TenantDao tenantDao;
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Alarm alarm) {
+ if (StringUtils.isEmpty(alarm.getType())) {
+ throw new DataValidationException("Alarm type should be specified!");
+ }
+ if (alarm.getOriginator() == null) {
+ throw new DataValidationException("Alarm originator should be specified!");
+ }
+ if (alarm.getSeverity() == null) {
+ throw new DataValidationException("Alarm severity should be specified!");
+ }
+ if (alarm.getStatus() == null) {
+ throw new DataValidationException("Alarm status should be specified!");
+ }
+ if (alarm.getTenantId() == null) {
+ throw new DataValidationException("Alarm should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(alarm.getTenantId(), alarm.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Alarm is referencing to non-existent tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java
new file mode 100644
index 0000000000..a89acb9631
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.ApiUsageState;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+@Component
+@AllArgsConstructor
+public class ApiUsageDataValidator extends DataValidator {
+
+ private final TenantDao tenantDao;
+
+ @Override
+ protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) {
+ if (apiUsageState.getTenantId() == null) {
+ throw new DataValidationException("ApiUsageState should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId());
+ if (tenant == null && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) {
+ throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!");
+ }
+ }
+ if (apiUsageState.getEntityId() == null) {
+ throw new DataValidationException("UsageRecord should be assigned to entity!");
+ } else if (apiUsageState.getEntityId().getEntityType() != EntityType.TENANT && apiUsageState.getEntityId().getEntityType() != EntityType.CUSTOMER) {
+ throw new DataValidationException("Only Tenant and Customer Usage Records are supported!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
new file mode 100644
index 0000000000..57d39ee781
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
@@ -0,0 +1,108 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.asset.AssetDao;
+import org.thingsboard.server.dao.asset.BaseAssetService;
+import org.thingsboard.server.dao.cache.EntitiesCacheManager;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+@Component
+public class AssetDataValidator extends DataValidator {
+
+ @Autowired
+ private AssetDao assetDao;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ private CustomerDao customerDao;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Autowired
+ private EntitiesCacheManager cacheManager;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, Asset asset) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ if (!BaseAssetService.TB_SERVICE_QUEUE.equals(asset.getType())) {
+ long maxAssets = profileConfiguration.getMaxAssets();
+ validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
+ }
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, Asset asset) {
+ Asset old = assetDao.findById(asset.getTenantId(), asset.getId().getId());
+ if (old == null) {
+ throw new DataValidationException("Can't update non existing asset!");
+ }
+ if (!old.getName().equals(asset.getName())) {
+ cacheManager.removeAssetFromCacheByName(tenantId, old.getName());
+ }
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Asset asset) {
+ if (StringUtils.isEmpty(asset.getType())) {
+ throw new DataValidationException("Asset type should be specified!");
+ }
+ if (StringUtils.isEmpty(asset.getName())) {
+ throw new DataValidationException("Asset name should be specified!");
+ }
+ if (asset.getTenantId() == null) {
+ throw new DataValidationException("Asset should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(tenantId, asset.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Asset is referencing to non-existent tenant!");
+ }
+ }
+ if (asset.getCustomerId() == null) {
+ asset.setCustomerId(new CustomerId(NULL_UUID));
+ } else if (!asset.getCustomerId().getId().equals(NULL_UUID)) {
+ Customer customer = customerDao.findById(tenantId, asset.getCustomerId().getId());
+ if (customer == null) {
+ throw new DataValidationException("Can't assign asset to non-existent customer!");
+ }
+ if (!customer.getTenantId().equals(asset.getTenantId())) {
+ throw new DataValidationException("Can't assign asset to customer from different tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java
new file mode 100644
index 0000000000..619e229595
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.audit.AuditLog;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+
+@Component
+public class AuditLogDataValidator extends DataValidator {
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, AuditLog auditLog) {
+ if (auditLog.getEntityId() == null) {
+ throw new DataValidationException("Entity Id should be specified!");
+ }
+ if (auditLog.getTenantId() == null) {
+ throw new DataValidationException("Tenant Id should be specified!");
+ }
+ if (auditLog.getUserId() == null) {
+ throw new DataValidationException("User Id should be specified!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java
new file mode 100644
index 0000000000..8184ff8e8e
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java
@@ -0,0 +1,123 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.thingsboard.server.common.data.BaseData;
+import org.thingsboard.server.common.data.DeviceProfile;
+import org.thingsboard.server.common.data.OtaPackageInfo;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.dao.device.DeviceProfileDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import java.util.Objects;
+
+public abstract class BaseOtaPackageDataValidator> extends DataValidator {
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ private DeviceProfileDao deviceProfileDao;
+
+ protected void validateImpl(OtaPackageInfo otaPackageInfo) {
+ if (otaPackageInfo.getTenantId() == null) {
+ throw new DataValidationException("OtaPackage should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("OtaPackage is referencing to non-existent tenant!");
+ }
+ }
+
+ if (otaPackageInfo.getDeviceProfileId() != null) {
+ DeviceProfile deviceProfile = deviceProfileDao.findById(otaPackageInfo.getTenantId(), otaPackageInfo.getDeviceProfileId().getId());
+ if (deviceProfile == null) {
+ throw new DataValidationException("OtaPackage is referencing to non-existent device profile!");
+ }
+ }
+
+ if (otaPackageInfo.getType() == null) {
+ throw new DataValidationException("Type should be specified!");
+ }
+
+ if (StringUtils.isEmpty(otaPackageInfo.getTitle())) {
+ throw new DataValidationException("OtaPackage title should be specified!");
+ }
+
+ if (StringUtils.isEmpty(otaPackageInfo.getVersion())) {
+ throw new DataValidationException("OtaPackage version should be specified!");
+ }
+
+ if (otaPackageInfo.getTitle().length() > 255) {
+ throw new DataValidationException("The length of title should be equal or shorter than 255");
+ }
+
+ if (otaPackageInfo.getVersion().length() > 255) {
+ throw new DataValidationException("The length of version should be equal or shorter than 255");
+ }
+ }
+
+ protected void validateUpdate(OtaPackageInfo otaPackage, OtaPackageInfo otaPackageOld) {
+ if (!otaPackageOld.getType().equals(otaPackage.getType())) {
+ throw new DataValidationException("Updating type is prohibited!");
+ }
+
+ if (!otaPackageOld.getTitle().equals(otaPackage.getTitle())) {
+ throw new DataValidationException("Updating otaPackage title is prohibited!");
+ }
+
+ if (!otaPackageOld.getVersion().equals(otaPackage.getVersion())) {
+ throw new DataValidationException("Updating otaPackage version is prohibited!");
+ }
+
+ if (!Objects.equals(otaPackage.getTag(), otaPackageOld.getTag())) {
+ throw new DataValidationException("Updating otaPackage tag is prohibited!");
+ }
+
+ if (!otaPackageOld.getDeviceProfileId().equals(otaPackage.getDeviceProfileId())) {
+ throw new DataValidationException("Updating otaPackage deviceProfile is prohibited!");
+ }
+
+ if (otaPackageOld.getFileName() != null && !otaPackageOld.getFileName().equals(otaPackage.getFileName())) {
+ throw new DataValidationException("Updating otaPackage file name is prohibited!");
+ }
+
+ if (otaPackageOld.getContentType() != null && !otaPackageOld.getContentType().equals(otaPackage.getContentType())) {
+ throw new DataValidationException("Updating otaPackage content type is prohibited!");
+ }
+
+ if (otaPackageOld.getChecksumAlgorithm() != null && !otaPackageOld.getChecksumAlgorithm().equals(otaPackage.getChecksumAlgorithm())) {
+ throw new DataValidationException("Updating otaPackage content type is prohibited!");
+ }
+
+ if (otaPackageOld.getChecksum() != null && !otaPackageOld.getChecksum().equals(otaPackage.getChecksum())) {
+ throw new DataValidationException("Updating otaPackage content type is prohibited!");
+ }
+
+ if (otaPackageOld.getDataSize() != null && !otaPackageOld.getDataSize().equals(otaPackage.getDataSize())) {
+ throw new DataValidationException("Updating otaPackage data size is prohibited!");
+ }
+
+ if (otaPackageOld.getUrl() != null && !otaPackageOld.getUrl().equals(otaPackage.getUrl())) {
+ throw new DataValidationException("Updating otaPackage URL is prohibited!");
+ }
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java
new file mode 100644
index 0000000000..d558c19e9e
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+
+@Component
+public class ClientRegistrationTemplateDataValidator extends DataValidator {
+
+ @Override
+ protected void validateCreate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
+ if (StringUtils.isEmpty(clientRegistrationTemplate.getProviderId())) {
+ throw new DataValidationException("Provider ID should be specified!");
+ }
+ if (clientRegistrationTemplate.getMapperConfig() == null) {
+ throw new DataValidationException("Mapper config should be specified!");
+ }
+ if (clientRegistrationTemplate.getMapperConfig().getType() == null) {
+ throw new DataValidationException("Mapper type should be specified!");
+ }
+ if (clientRegistrationTemplate.getMapperConfig().getBasic() == null) {
+ throw new DataValidationException("Basic mapper config should be specified!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java
new file mode 100644
index 0000000000..f7eceec4d5
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+
+@Component
+public class ComponentDescriptorDataValidator extends DataValidator {
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, ComponentDescriptor plugin) {
+ if (plugin.getType() == null) {
+ throw new DataValidationException("Component type should be specified!");
+ }
+ if (plugin.getScope() == null) {
+ throw new DataValidationException("Component scope should be specified!");
+ }
+ if (StringUtils.isEmpty(plugin.getName())) {
+ throw new DataValidationException("Component name should be specified!");
+ }
+ if (StringUtils.isEmpty(plugin.getClazz())) {
+ throw new DataValidationException("Component clazz should be specified!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java
new file mode 100644
index 0000000000..753d3a8528
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.customer.CustomerServiceImpl;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+@Component
+public class CustomerDataValidator extends DataValidator {
+
+ @Autowired
+ private CustomerDao customerDao;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, Customer customer) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxCustomers = profileConfiguration.getMaxCustomers();
+
+ validateNumberOfEntitiesPerTenant(tenantId, customerDao, maxCustomers, EntityType.CUSTOMER);
+ customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
+ c -> {
+ throw new DataValidationException("Customer with such title already exists!");
+ }
+ );
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, Customer customer) {
+ customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
+ c -> {
+ if (!c.getId().equals(customer.getId())) {
+ throw new DataValidationException("Customer with such title already exists!");
+ }
+ }
+ );
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Customer customer) {
+ if (StringUtils.isEmpty(customer.getTitle())) {
+ throw new DataValidationException("Customer title should be specified!");
+ }
+ if (customer.getTitle().equals(CustomerServiceImpl.PUBLIC_CUSTOMER_TITLE)) {
+ throw new DataValidationException("'Public' title for customer is system reserved!");
+ }
+ if (!StringUtils.isEmpty(customer.getEmail())) {
+ validateEmail(customer.getEmail());
+ }
+ if (customer.getTenantId() == null) {
+ throw new DataValidationException("Customer should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(tenantId, customer.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Customer is referencing to non-existent tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java
new file mode 100644
index 0000000000..ee226af853
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Dashboard;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.dashboard.DashboardDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+@Component
+public class DashboardDataValidator extends DataValidator {
+
+ @Autowired
+ private DashboardDao dashboardDao;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, Dashboard data) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxDashboards = profileConfiguration.getMaxDashboards();
+ validateNumberOfEntitiesPerTenant(tenantId, dashboardDao, maxDashboards, EntityType.DASHBOARD);
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Dashboard dashboard) {
+ if (StringUtils.isEmpty(dashboard.getTitle())) {
+ throw new DataValidationException("Dashboard title should be specified!");
+ }
+ if (dashboard.getTenantId() == null) {
+ throw new DataValidationException("Dashboard should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(tenantId, dashboard.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java
new file mode 100644
index 0000000000..5fca062d46
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java
@@ -0,0 +1,75 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.dao.device.DeviceCredentialsDao;
+import org.thingsboard.server.dao.device.DeviceService;
+import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+
+@Component
+public class DeviceCredentialsDataValidator extends DataValidator {
+
+ @Autowired
+ private DeviceCredentialsDao deviceCredentialsDao;
+
+ @Autowired
+ private DeviceService deviceService;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, DeviceCredentials deviceCredentials) {
+ if (deviceCredentialsDao.findByDeviceId(tenantId, deviceCredentials.getDeviceId().getId()) != null) {
+ throw new DeviceCredentialsValidationException("Credentials for this device are already specified!");
+ }
+ if (deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId()) != null) {
+ throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!");
+ }
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, DeviceCredentials deviceCredentials) {
+ if (deviceCredentialsDao.findById(tenantId, deviceCredentials.getUuidId()) == null) {
+ throw new DeviceCredentialsValidationException("Unable to update non-existent device credentials!");
+ }
+ DeviceCredentials existingCredentials = deviceCredentialsDao.findByCredentialsId(tenantId, deviceCredentials.getCredentialsId());
+ if (existingCredentials != null && !existingCredentials.getId().equals(deviceCredentials.getId())) {
+ throw new DeviceCredentialsValidationException("Device credentials are already assigned to another device!");
+ }
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, DeviceCredentials deviceCredentials) {
+ if (deviceCredentials.getDeviceId() == null) {
+ throw new DeviceCredentialsValidationException("Device credentials should be assigned to device!");
+ }
+ if (deviceCredentials.getCredentialsType() == null) {
+ throw new DeviceCredentialsValidationException("Device credentials type should be specified!");
+ }
+ if (StringUtils.isEmpty(deviceCredentials.getCredentialsId())) {
+ throw new DeviceCredentialsValidationException("Device credentials id should be specified!");
+ }
+ Device device = deviceService.findDeviceById(tenantId, deviceCredentials.getDeviceId());
+ if (device == null) {
+ throw new DeviceCredentialsValidationException("Can't assign device credentials to non-existent device!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java
new file mode 100644
index 0000000000..fc991cee91
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java
@@ -0,0 +1,147 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.OtaPackage;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.ota.OtaPackageType;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.cache.EntitiesCacheManager;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.device.DeviceDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.ota.OtaPackageService;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import java.util.Optional;
+
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+@Component
+public class DeviceDataValidator extends DataValidator {
+
+ @Autowired
+ private DeviceDao deviceDao;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ private CustomerDao customerDao;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Autowired
+ private OtaPackageService otaPackageService;
+
+ @Autowired
+ private EntitiesCacheManager cacheManager;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, Device device) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxDevices = profileConfiguration.getMaxDevices();
+ validateNumberOfEntitiesPerTenant(tenantId, deviceDao, maxDevices, EntityType.DEVICE);
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, Device device) {
+ Device old = deviceDao.findById(device.getTenantId(), device.getId().getId());
+ if (old == null) {
+ throw new DataValidationException("Can't update non existing device!");
+ }
+ if (!old.getName().equals(device.getName())) {
+ cacheManager.removeDeviceFromCacheByName(tenantId, old.getName());
+ cacheManager.removeDeviceFromCacheById(tenantId, device.getId());
+ }
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Device device) {
+ if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) {
+ throw new DataValidationException("Device name should be specified!");
+ }
+ if (device.getTenantId() == null) {
+ throw new DataValidationException("Device should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(device.getTenantId(), device.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Device is referencing to non-existent tenant!");
+ }
+ }
+ if (device.getCustomerId() == null) {
+ device.setCustomerId(new CustomerId(NULL_UUID));
+ } else if (!device.getCustomerId().getId().equals(NULL_UUID)) {
+ Customer customer = customerDao.findById(device.getTenantId(), device.getCustomerId().getId());
+ if (customer == null) {
+ throw new DataValidationException("Can't assign device to non-existent customer!");
+ }
+ if (!customer.getTenantId().getId().equals(device.getTenantId().getId())) {
+ throw new DataValidationException("Can't assign device to customer from different tenant!");
+ }
+ }
+ Optional.ofNullable(device.getDeviceData())
+ .flatMap(deviceData -> Optional.ofNullable(deviceData.getTransportConfiguration()))
+ .ifPresent(DeviceTransportConfiguration::validate);
+
+ if (device.getFirmwareId() != null) {
+ OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, device.getFirmwareId());
+ if (firmware == null) {
+ throw new DataValidationException("Can't assign non-existent firmware!");
+ }
+ if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) {
+ throw new DataValidationException("Can't assign firmware with type: " + firmware.getType());
+ }
+ if (firmware.getData() == null && !firmware.hasUrl()) {
+ throw new DataValidationException("Can't assign firmware with empty data!");
+ }
+ if (!firmware.getDeviceProfileId().equals(device.getDeviceProfileId())) {
+ throw new DataValidationException("Can't assign firmware with different deviceProfile!");
+ }
+ }
+
+ if (device.getSoftwareId() != null) {
+ OtaPackage software = otaPackageService.findOtaPackageById(tenantId, device.getSoftwareId());
+ if (software == null) {
+ throw new DataValidationException("Can't assign non-existent software!");
+ }
+ if (!software.getType().equals(OtaPackageType.SOFTWARE)) {
+ throw new DataValidationException("Can't assign software with type: " + software.getType());
+ }
+ if (software.getData() == null && !software.hasUrl()) {
+ throw new DataValidationException("Can't assign software with empty data!");
+ }
+ if (!software.getDeviceProfileId().equals(device.getDeviceProfileId())) {
+ throw new DataValidationException("Can't assign firmware with different deviceProfile!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
new file mode 100644
index 0000000000..adc8cf9fb1
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
@@ -0,0 +1,524 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import com.google.protobuf.Descriptors;
+import com.google.protobuf.DynamicMessage;
+import com.squareup.wire.Syntax;
+import com.squareup.wire.schema.Field;
+import com.squareup.wire.schema.Location;
+import com.squareup.wire.schema.internal.parser.EnumElement;
+import com.squareup.wire.schema.internal.parser.FieldElement;
+import com.squareup.wire.schema.internal.parser.MessageElement;
+import com.squareup.wire.schema.internal.parser.OneOfElement;
+import com.squareup.wire.schema.internal.parser.ProtoFileElement;
+import com.squareup.wire.schema.internal.parser.ProtoParser;
+import com.squareup.wire.schema.internal.parser.TypeElement;
+import org.eclipse.leshan.core.util.SecurityUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.thingsboard.server.common.data.DashboardInfo;
+import org.thingsboard.server.common.data.DeviceProfile;
+import org.thingsboard.server.common.data.DeviceProfileProvisionType;
+import org.thingsboard.server.common.data.OtaPackage;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
+import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration;
+import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration;
+import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration;
+import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
+import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
+import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
+import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
+import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
+import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
+import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential;
+import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
+import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential;
+import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.ota.OtaPackageType;
+import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.msg.EncryptionUtil;
+import org.thingsboard.server.common.msg.queue.ServiceType;
+import org.thingsboard.server.dao.dashboard.DashboardService;
+import org.thingsboard.server.dao.device.DeviceDao;
+import org.thingsboard.server.dao.device.DeviceProfileDao;
+import org.thingsboard.server.dao.device.DeviceProfileService;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
+import org.thingsboard.server.dao.ota.OtaPackageService;
+import org.thingsboard.server.dao.rule.RuleChainService;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.thingsboard.server.queue.QueueService;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@Component
+public class DeviceProfileDataValidator extends DataValidator {
+
+ private static final Location LOCATION = new Location("", "", -1, -1);
+ private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema";
+ private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema";
+ private static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema";
+ private static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema";
+ @Autowired
+ private DeviceProfileDao deviceProfileDao;
+ @Autowired
+ @Lazy
+ private DeviceProfileService deviceProfileService;
+ @Autowired
+ private DeviceDao deviceDao;
+ @Autowired
+ private TenantDao tenantDao;
+ @Autowired
+ @Lazy
+ private QueueService queueService;
+ @Autowired
+ private OtaPackageService otaPackageService;
+ @Autowired
+ private RuleChainService ruleChainService;
+ @Autowired
+ private DashboardService dashboardService;
+
+ private static String invalidSchemaProvidedMessage(String schemaName) {
+ return "[Transport Configuration] invalid " + schemaName + " provided!";
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, DeviceProfile deviceProfile) {
+ if (org.thingsboard.server.common.data.StringUtils.isEmpty(deviceProfile.getName())) {
+ throw new DataValidationException("Device profile name should be specified!");
+ }
+ if (deviceProfile.getType() == null) {
+ throw new DataValidationException("Device profile type should be specified!");
+ }
+ if (deviceProfile.getTransportType() == null) {
+ throw new DataValidationException("Device profile transport type should be specified!");
+ }
+ if (deviceProfile.getTenantId() == null) {
+ throw new DataValidationException("Device profile should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(deviceProfile.getTenantId(), deviceProfile.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Device profile is referencing to non-existent tenant!");
+ }
+ }
+ if (deviceProfile.isDefault()) {
+ DeviceProfile defaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(tenantId);
+ if (defaultDeviceProfile != null && !defaultDeviceProfile.getId().equals(deviceProfile.getId())) {
+ throw new DataValidationException("Another default device profile is present in scope of current tenant!");
+ }
+ }
+ if (!org.thingsboard.server.common.data.StringUtils.isEmpty(deviceProfile.getDefaultQueueName()) && queueService != null) {
+ if (!queueService.getQueuesByServiceType(ServiceType.TB_RULE_ENGINE).contains(deviceProfile.getDefaultQueueName())) {
+ throw new DataValidationException("Device profile is referencing to non-existent queue!");
+ }
+ }
+ if (deviceProfile.getProvisionType() == null) {
+ deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
+ }
+ DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
+ transportConfiguration.validate();
+ if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) {
+ MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
+ if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) {
+ ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration =
+ (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration();
+ validateProtoSchemas(protoTransportPayloadConfiguration);
+ validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration);
+ validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration);
+ }
+ } else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) {
+ CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration;
+ CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration();
+ if (coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration) {
+ DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration;
+ TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration();
+ if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) {
+ ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration;
+ validateProtoSchemas(protoTransportPayloadConfiguration);
+ validateTelemetryDynamicMessageFields(protoTransportPayloadConfiguration);
+ validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration);
+ }
+ }
+ } else if (transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) {
+ List lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap();
+ if (lwM2MBootstrapServersConfigurations != null) {
+ validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations,
+ ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable());
+ for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) {
+ validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential);
+ }
+ }
+ }
+
+ List profileAlarms = deviceProfile.getProfileData().getAlarms();
+
+ if (!CollectionUtils.isEmpty(profileAlarms)) {
+ Set alarmTypes = new HashSet<>();
+ for (DeviceProfileAlarm alarm : profileAlarms) {
+ String alarmType = alarm.getAlarmType();
+ if (StringUtils.isEmpty(alarmType)) {
+ throw new DataValidationException("Alarm rule type should be specified!");
+ }
+ if (!alarmTypes.add(alarmType)) {
+ throw new DataValidationException(String.format("Can't create device profile with the same alarm rule types: \"%s\"!", alarmType));
+ }
+ }
+ }
+
+ if (deviceProfile.getDefaultRuleChainId() != null) {
+ RuleChain ruleChain = ruleChainService.findRuleChainById(tenantId, deviceProfile.getDefaultRuleChainId());
+ if (ruleChain == null) {
+ throw new DataValidationException("Can't assign non-existent rule chain!");
+ }
+ }
+
+ if (deviceProfile.getDefaultDashboardId() != null) {
+ DashboardInfo dashboard = dashboardService.findDashboardInfoById(tenantId, deviceProfile.getDefaultDashboardId());
+ if (dashboard == null) {
+ throw new DataValidationException("Can't assign non-existent dashboard!");
+ }
+ }
+
+ if (deviceProfile.getFirmwareId() != null) {
+ OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getFirmwareId());
+ if (firmware == null) {
+ throw new DataValidationException("Can't assign non-existent firmware!");
+ }
+ if (!firmware.getType().equals(OtaPackageType.FIRMWARE)) {
+ throw new DataValidationException("Can't assign firmware with type: " + firmware.getType());
+ }
+ if (firmware.getData() == null && !firmware.hasUrl()) {
+ throw new DataValidationException("Can't assign firmware with empty data!");
+ }
+ if (!firmware.getDeviceProfileId().equals(deviceProfile.getId())) {
+ throw new DataValidationException("Can't assign firmware with different deviceProfile!");
+ }
+ }
+
+ if (deviceProfile.getSoftwareId() != null) {
+ OtaPackage software = otaPackageService.findOtaPackageById(tenantId, deviceProfile.getSoftwareId());
+ if (software == null) {
+ throw new DataValidationException("Can't assign non-existent software!");
+ }
+ if (!software.getType().equals(OtaPackageType.SOFTWARE)) {
+ throw new DataValidationException("Can't assign software with type: " + software.getType());
+ }
+ if (software.getData() == null && !software.hasUrl()) {
+ throw new DataValidationException("Can't assign software with empty data!");
+ }
+ if (!software.getDeviceProfileId().equals(deviceProfile.getId())) {
+ throw new DataValidationException("Can't assign firmware with different deviceProfile!");
+ }
+ }
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, DeviceProfile deviceProfile) {
+ DeviceProfile old = deviceProfileDao.findById(deviceProfile.getTenantId(), deviceProfile.getId().getId());
+ if (old == null) {
+ throw new DataValidationException("Can't update non existing device profile!");
+ }
+ boolean profileTypeChanged = !old.getType().equals(deviceProfile.getType());
+ boolean transportTypeChanged = !old.getTransportType().equals(deviceProfile.getTransportType());
+ if (profileTypeChanged || transportTypeChanged) {
+ Long profileDeviceCount = deviceDao.countDevicesByDeviceProfileId(deviceProfile.getTenantId(), deviceProfile.getId().getId());
+ if (profileDeviceCount > 0) {
+ String message = null;
+ if (profileTypeChanged) {
+ message = "Can't change device profile type because devices referenced it!";
+ } else if (transportTypeChanged) {
+ message = "Can't change device profile transport type because devices referenced it!";
+ }
+ throw new DataValidationException(message);
+ }
+ }
+ }
+
+ private void validateProtoSchemas(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {
+ try {
+ validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA);
+ validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA);
+ validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema(), RPC_REQUEST_PROTO_SCHEMA);
+ validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcResponseProtoSchema(), RPC_RESPONSE_PROTO_SCHEMA);
+ } catch (Exception exception) {
+ throw new DataValidationException(exception.getMessage());
+ }
+ }
+
+ private void validateTransportProtoSchema(String schema, String schemaName) throws IllegalArgumentException {
+ ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray());
+ ProtoFileElement protoFileElement;
+ try {
+ protoFileElement = schemaParser.readProtoFile();
+ } catch (Exception e) {
+ throw new IllegalArgumentException("[Transport Configuration] failed to parse " + schemaName + " due to: " + e.getMessage());
+ }
+ checkProtoFileSyntax(schemaName, protoFileElement);
+ checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!");
+ checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!");
+ checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!");
+ checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!");
+ checkTypeElements(schemaName, protoFileElement);
+ }
+
+ private void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) {
+ if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) {
+ throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() +
+ " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!");
+ }
+ }
+
+ private void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage) {
+ if (!isEmptySettings) {
+ throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + invalidSettingsMessage);
+ }
+ }
+
+ private void checkTypeElements(String schemaName, ProtoFileElement protoFileElement) {
+ List types = protoFileElement.getTypes();
+ if (!types.isEmpty()) {
+ if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) {
+ throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!");
+ } else {
+ checkEnumElements(schemaName, getEnumElements(types));
+ checkMessageElements(schemaName, getMessageTypes(types));
+ }
+ } else {
+ throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!");
+ }
+ }
+
+ private void checkFieldElements(String schemaName, List fieldElements) {
+ if (!fieldElements.isEmpty()) {
+ boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> {
+ Field.Label label = fieldElement.getLabel();
+ return label != null && label.equals(Field.Label.REQUIRED);
+ });
+ if (hasRequiredLabel) {
+ throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!");
+ }
+ boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null);
+ if (hasDefaultValue) {
+ throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!");
+ }
+ }
+ }
+
+ private void checkEnumElements(String schemaName, List enumTypes) {
+ if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) {
+ throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!");
+ }
+ if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) {
+ throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!");
+ }
+ }
+
+ private void checkMessageElements(String schemaName, List messageElementsList) {
+ if (!messageElementsList.isEmpty()) {
+ messageElementsList.forEach(messageElement -> {
+ checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(),
+ " Message definition groups don't support!");
+ checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(),
+ " Message definition options don't support!");
+ checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(),
+ " Message definition extensions don't support!");
+ checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(),
+ " Message definition reserved elements don't support!");
+ checkFieldElements(schemaName, messageElement.getFields());
+ List oneOfs = messageElement.getOneOfs();
+ if (!oneOfs.isEmpty()) {
+ oneOfs.forEach(oneOfElement -> {
+ checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(),
+ " OneOf definition groups don't support!");
+ checkFieldElements(schemaName, oneOfElement.getFields());
+ });
+ }
+ List nestedTypes = messageElement.getNestedTypes();
+ if (!nestedTypes.isEmpty()) {
+ List nestedEnumTypes = getEnumElements(nestedTypes);
+ if (!nestedEnumTypes.isEmpty()) {
+ checkEnumElements(schemaName, nestedEnumTypes);
+ }
+ List nestedMessageTypes = getMessageTypes(nestedTypes);
+ checkMessageElements(schemaName, nestedMessageTypes);
+ }
+ });
+ }
+ }
+
+ private List getMessageTypes(List types) {
+ return types.stream()
+ .filter(typeElement -> typeElement instanceof MessageElement)
+ .map(typeElement -> (MessageElement) typeElement)
+ .collect(Collectors.toList());
+ }
+
+ private List getEnumElements(List types) {
+ return types.stream()
+ .filter(typeElement -> typeElement instanceof EnumElement)
+ .map(typeElement -> (EnumElement) typeElement)
+ .collect(Collectors.toList());
+ }
+
+ private void validateTelemetryDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {
+ String deviceTelemetryProtoSchema = protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema();
+ Descriptors.Descriptor telemetryDynamicMessageDescriptor = protoTransportPayloadTypeConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema);
+ if (telemetryDynamicMessageDescriptor == null) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Failed to get telemetryDynamicMessageDescriptor!");
+ } else {
+ List fields = telemetryDynamicMessageDescriptor.getFields();
+ if (CollectionUtils.isEmpty(fields)) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " " + telemetryDynamicMessageDescriptor.getName() + " fields is empty!");
+ } else if (fields.size() == 2) {
+ Descriptors.FieldDescriptor tsFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("ts");
+ Descriptors.FieldDescriptor valuesFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("values");
+ if (tsFieldDescriptor != null && valuesFieldDescriptor != null) {
+ if (!Descriptors.FieldDescriptor.Type.MESSAGE.equals(valuesFieldDescriptor.getType())) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'values' has invalid data type. Only message type is supported!");
+ }
+ if (!Descriptors.FieldDescriptor.Type.INT64.equals(tsFieldDescriptor.getType())) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid data type. Only int64 type is supported!");
+ }
+ if (!tsFieldDescriptor.hasOptionalKeyword()) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid label. Field 'ts' should have optional keyword!");
+ }
+ }
+ }
+ }
+ }
+
+ private void validateRpcRequestDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) {
+ DynamicMessage.Builder rpcRequestDynamicMessageBuilder = protoTransportPayloadTypeConfiguration.getRpcRequestDynamicMessageBuilder(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema());
+ Descriptors.Descriptor rpcRequestDynamicMessageDescriptor = rpcRequestDynamicMessageBuilder.getDescriptorForType();
+ if (rpcRequestDynamicMessageDescriptor == null) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get rpcRequestDynamicMessageDescriptor!");
+ } else {
+ if (CollectionUtils.isEmpty(rpcRequestDynamicMessageDescriptor.getFields()) || rpcRequestDynamicMessageDescriptor.getFields().size() != 3) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " " + rpcRequestDynamicMessageDescriptor.getName() + " message should always contains 3 fields: method, requestId and params!");
+ }
+ Descriptors.FieldDescriptor methodFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("method");
+ if (methodFieldDescriptor == null) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: method!");
+ } else {
+ if (!Descriptors.FieldDescriptor.Type.STRING.equals(methodFieldDescriptor.getType())) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid data type. Only string type is supported!");
+ }
+ if (methodFieldDescriptor.isRepeated()) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid label!");
+ }
+ }
+ Descriptors.FieldDescriptor requestIdFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("requestId");
+ if (requestIdFieldDescriptor == null) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: requestId!");
+ } else {
+ if (!Descriptors.FieldDescriptor.Type.INT32.equals(requestIdFieldDescriptor.getType())) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid data type. Only int32 type is supported!");
+ }
+ if (requestIdFieldDescriptor.isRepeated()) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid label!");
+ }
+ }
+ Descriptors.FieldDescriptor paramsFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("params");
+ if (paramsFieldDescriptor == null) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: params!");
+ } else {
+ if (paramsFieldDescriptor.isRepeated()) {
+ throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'params' has invalid label!");
+ }
+ }
+ }
+ }
+
+ private void validateLwm2mServersConfigOfBootstrapForClient(List lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) {
+ Set uris = new HashSet<>();
+ Set shortServerIds = new HashSet<>();
+ for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) {
+ AbstractLwM2MBootstrapServerCredential serverConfig = (AbstractLwM2MBootstrapServerCredential) bootstrapServerCredential;
+ if (!isBootstrapServerUpdateEnable && serverConfig.isBootstrapServerIs()) {
+ throw new DeviceCredentialsValidationException("Bootstrap config must not include \"Bootstrap Server\". \"Include Bootstrap Server updates\" is " + isBootstrapServerUpdateEnable + ".");
+ }
+ String server = serverConfig.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server" + " shortServerId: " + serverConfig.getShortServerId() + ":";
+ if (serverConfig.getShortServerId() < 1 || serverConfig.getShortServerId() > 65534) {
+ throw new DeviceCredentialsValidationException(server + " ShortServerId must not be less than 1 and more than 65534!");
+ }
+ if (!shortServerIds.add(serverConfig.getShortServerId())) {
+ throw new DeviceCredentialsValidationException(server + " \"Short server Id\" value = " + serverConfig.getShortServerId() + ". This value must be a unique value for all servers!");
+ }
+ String uri = serverConfig.getHost() + ":" + serverConfig.getPort();
+ if (!uris.add(uri)) {
+ throw new DeviceCredentialsValidationException(server + " \"Host + port\" value = " + uri + ". This value must be a unique value for all servers!");
+ }
+ Integer port;
+ if (LwM2MSecurityMode.NO_SEC.equals(serverConfig.getSecurityMode())) {
+ port = serverConfig.isBootstrapServerIs() ? 5687 : 5685;
+ } else {
+ port = serverConfig.isBootstrapServerIs() ? 5688 : 5686;
+ }
+ if (serverConfig.getPort() == null || serverConfig.getPort().intValue() != port) {
+ String errMsg = server + " \"Port\" value = " + serverConfig.getPort() + ". This value for security " + serverConfig.getSecurityMode().name() + " must be " + port + "!";
+ throw new DeviceCredentialsValidationException(errMsg);
+ }
+ }
+ }
+
+ private void validateLwm2mServersCredentialOfBootstrapForClient(LwM2MBootstrapServerCredential bootstrapServerConfig) {
+ String server;
+ switch (bootstrapServerConfig.getSecurityMode()) {
+ case NO_SEC:
+ case PSK:
+ break;
+ case RPK:
+ RPKLwM2MBootstrapServerCredential rpkServerCredentials = (RPKLwM2MBootstrapServerCredential) bootstrapServerConfig;
+ server = rpkServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server";
+ if (StringUtils.isEmpty(rpkServerCredentials.getServerPublicKey())) {
+ throw new DeviceCredentialsValidationException(server + " RPK public key must be specified!");
+ }
+ try {
+ String pubkRpkSever = EncryptionUtil.pubkTrimNewLines(rpkServerCredentials.getServerPublicKey());
+ rpkServerCredentials.setServerPublicKey(pubkRpkSever);
+ SecurityUtil.publicKey.decode(rpkServerCredentials.getDecodedCServerPublicKey());
+ } catch (Exception e) {
+ throw new DeviceCredentialsValidationException(server + " RPK public key must be in standard [RFC7250] and then encoded to Base64 format!");
+ }
+ break;
+ case X509:
+ X509LwM2MBootstrapServerCredential x509ServerCredentials = (X509LwM2MBootstrapServerCredential) bootstrapServerConfig;
+ server = x509ServerCredentials.isBootstrapServerIs() ? "Bootstrap Server" : "LwM2M Server";
+ if (StringUtils.isEmpty(x509ServerCredentials.getServerPublicKey())) {
+ throw new DeviceCredentialsValidationException(server + " X509 certificate must be specified!");
+ }
+
+ try {
+ String certServer = EncryptionUtil.certTrimNewLines(x509ServerCredentials.getServerPublicKey());
+ x509ServerCredentials.setServerPublicKey(certServer);
+ SecurityUtil.certificate.decode(x509ServerCredentials.getDecodedCServerPublicKey());
+ } catch (Exception e) {
+ throw new DeviceCredentialsValidationException(server + " X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!");
+ }
+ break;
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java
new file mode 100644
index 0000000000..be01ec5c9b
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.cache.EntitiesCacheManager;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.edge.EdgeDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+@Component
+@AllArgsConstructor
+public class EdgeDataValidator extends DataValidator {
+
+ private final EdgeDao edgeDao;
+ private final TenantDao tenantDao;
+ private final CustomerDao customerDao;
+ private final EntitiesCacheManager cacheManager;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, Edge edge) {
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, Edge edge) {
+ Edge old = edgeDao.findById(edge.getTenantId(), edge.getId().getId());
+ if (!old.getName().equals(edge.getName())) {
+ cacheManager.removeEdgeFromCacheByName(tenantId, old.getName());
+ }
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Edge edge) {
+ if (org.springframework.util.StringUtils.isEmpty(edge.getType())) {
+ throw new DataValidationException("Edge type should be specified!");
+ }
+ if (org.springframework.util.StringUtils.isEmpty(edge.getName())) {
+ throw new DataValidationException("Edge name should be specified!");
+ }
+ if (org.springframework.util.StringUtils.isEmpty(edge.getSecret())) {
+ throw new DataValidationException("Edge secret should be specified!");
+ }
+ if (StringUtils.isEmpty(edge.getRoutingKey())) {
+ throw new DataValidationException("Edge routing key should be specified!");
+ }
+ if (edge.getTenantId() == null) {
+ throw new DataValidationException("Edge should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(edge.getTenantId(), edge.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Edge is referencing to non-existent tenant!");
+ }
+ }
+ if (edge.getCustomerId() == null) {
+ edge.setCustomerId(new CustomerId(NULL_UUID));
+ } else if (!edge.getCustomerId().getId().equals(NULL_UUID)) {
+ Customer customer = customerDao.findById(edge.getTenantId(), edge.getCustomerId().getId());
+ if (customer == null) {
+ throw new DataValidationException("Can't assign edge to non-existent customer!");
+ }
+ if (!customer.getTenantId().getId().equals(edge.getTenantId().getId())) {
+ throw new DataValidationException("Can't assign edge to customer from different tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeEventDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeEventDataValidator.java
new file mode 100644
index 0000000000..5c437401b3
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeEventDataValidator.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.edge.EdgeEvent;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+
+@Component
+public class EdgeEventDataValidator extends DataValidator {
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, EdgeEvent edgeEvent) {
+ if (edgeEvent.getEdgeId() == null) {
+ throw new DataValidationException("Edge id should be specified!");
+ }
+ if (edgeEvent.getAction() == null) {
+ throw new DataValidationException("Edge Event action should be specified!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java
new file mode 100644
index 0000000000..a62e1afc9f
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.EntityView;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.entityview.EntityViewDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+@Component
+@AllArgsConstructor
+public class EntityViewDataValidator extends DataValidator {
+
+ private final EntityViewDao entityViewDao;
+ private final TenantDao tenantDao;
+ private final CustomerDao customerDao;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, EntityView entityView) {
+ entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
+ .ifPresent(e -> {
+ throw new DataValidationException("Entity view with such name already exists!");
+ });
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, EntityView entityView) {
+ entityViewDao.findEntityViewByTenantIdAndName(entityView.getTenantId().getId(), entityView.getName())
+ .ifPresent(e -> {
+ if (!e.getUuidId().equals(entityView.getUuidId())) {
+ throw new DataValidationException("Entity view with such name already exists!");
+ }
+ });
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, EntityView entityView) {
+ if (StringUtils.isEmpty(entityView.getType())) {
+ throw new DataValidationException("Entity View type should be specified!");
+ }
+ if (StringUtils.isEmpty(entityView.getName())) {
+ throw new DataValidationException("Entity view name should be specified!");
+ }
+ if (entityView.getTenantId() == null) {
+ throw new DataValidationException("Entity view should be assigned to tenant!");
+ } else {
+ Tenant tenant = tenantDao.findById(tenantId, entityView.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Entity view is referencing to non-existent tenant!");
+ }
+ }
+ if (entityView.getCustomerId() == null) {
+ entityView.setCustomerId(new CustomerId(NULL_UUID));
+ } else if (!entityView.getCustomerId().getId().equals(NULL_UUID)) {
+ Customer customer = customerDao.findById(tenantId, entityView.getCustomerId().getId());
+ if (customer == null) {
+ throw new DataValidationException("Can't assign entity view to non-existent customer!");
+ }
+ if (!customer.getTenantId().getId().equals(entityView.getTenantId().getId())) {
+ throw new DataValidationException("Can't assign entity view to customer from different tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java
new file mode 100644
index 0000000000..ae7b7542a4
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+
+@Component
+public class EventDataValidator extends DataValidator {
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Event event) {
+ if (event.getEntityId() == null) {
+ throw new DataValidationException("Entity id should be specified!.");
+ }
+ if (StringUtils.isEmpty(event.getType())) {
+ throw new DataValidationException("Event type should be specified!.");
+ }
+ if (event.getBody() == null) {
+ throw new DataValidationException("Event body should be specified!.");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java
new file mode 100644
index 0000000000..8b261ad226
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.OtaPackage;
+import org.thingsboard.server.common.data.StringUtils;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.ota.OtaPackageDao;
+import org.thingsboard.server.dao.ota.OtaPackageService;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+
+import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE;
+
+@Component
+public class OtaPackageDataValidator extends BaseOtaPackageDataValidator {
+
+ @Autowired
+ private OtaPackageDao otaPackageDao;
+
+ @Autowired
+ @Lazy
+ private OtaPackageService otaPackageService;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, OtaPackage otaPackage) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
+ validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, OtaPackage otaPackage) {
+ validateImpl(otaPackage);
+
+ if (!otaPackage.hasUrl()) {
+ if (StringUtils.isEmpty(otaPackage.getFileName())) {
+ throw new DataValidationException("OtaPackage file name should be specified!");
+ }
+
+ if (StringUtils.isEmpty(otaPackage.getContentType())) {
+ throw new DataValidationException("OtaPackage content type should be specified!");
+ }
+
+ if (otaPackage.getChecksumAlgorithm() == null) {
+ throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
+ }
+ if (StringUtils.isEmpty(otaPackage.getChecksum())) {
+ throw new DataValidationException("OtaPackage checksum should be specified!");
+ }
+
+ String currentChecksum;
+
+ currentChecksum = otaPackageService.generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
+
+ if (!currentChecksum.equals(otaPackage.getChecksum())) {
+ throw new DataValidationException("Wrong otaPackage file!");
+ }
+ } else {
+ if (otaPackage.getData() != null) {
+ throw new DataValidationException("File can't be saved if URL present!");
+ }
+ }
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, OtaPackage otaPackage) {
+ OtaPackage otaPackageOld = otaPackageDao.findById(tenantId, otaPackage.getUuidId());
+
+ validateUpdate(otaPackage, otaPackageOld);
+
+ if (otaPackageOld.getData() != null && !otaPackageOld.getData().equals(otaPackage.getData())) {
+ throw new DataValidationException("Updating otaPackage data is prohibited!");
+ }
+
+ if (otaPackageOld.getData() == null && otaPackage.getData() != null) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
+ validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java
new file mode 100644
index 0000000000..d880dfe42c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java
@@ -0,0 +1,40 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.OtaPackageInfo;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.ota.OtaPackageInfoDao;
+
+@Component
+public class OtaPackageInfoDataValidator extends BaseOtaPackageDataValidator {
+
+ @Autowired
+ private OtaPackageInfoDao otaPackageInfoDao;
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, OtaPackageInfo otaPackageInfo) {
+ validateImpl(otaPackageInfo);
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, OtaPackageInfo otaPackage) {
+ OtaPackageInfo otaPackageOld = otaPackageInfoDao.findById(tenantId, otaPackage.getUuidId());
+ validateUpdate(otaPackage, otaPackageOld);
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java
new file mode 100644
index 0000000000..90ac54577a
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java
@@ -0,0 +1,82 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.TbResource;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.resource.TbResourceDao;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE;
+
+@Component
+public class ResourceDataValidator extends DataValidator {
+
+ @Autowired
+ private TbResourceDao resourceDao;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, TbResource resource) {
+ if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId)) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes();
+ validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length(), TB_RESOURCE);
+ }
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, TbResource resource) {
+ if (StringUtils.isEmpty(resource.getTitle())) {
+ throw new DataValidationException("Resource title should be specified!");
+ }
+ if (resource.getResourceType() == null) {
+ throw new DataValidationException("Resource type should be specified!");
+ }
+ if (StringUtils.isEmpty(resource.getFileName())) {
+ throw new DataValidationException("Resource file name should be specified!");
+ }
+ if (StringUtils.isEmpty(resource.getResourceKey())) {
+ throw new DataValidationException("Resource key should be specified!");
+ }
+ if (resource.getTenantId() == null) {
+ resource.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
+ }
+ if (!resource.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+ Tenant tenant = tenantDao.findById(tenantId, resource.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Resource is referencing to non-existent tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
new file mode 100644
index 0000000000..f36b4d23fa
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.rule.RuleChain;
+import org.thingsboard.server.common.data.rule.RuleChainType;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.rule.RuleChainDao;
+import org.thingsboard.server.dao.rule.RuleChainService;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+import org.thingsboard.server.dao.tenant.TenantDao;
+
+@Component
+public class RuleChainDataValidator extends DataValidator {
+
+ @Autowired
+ private RuleChainDao ruleChainDao;
+
+ @Autowired
+ @Lazy
+ private RuleChainService ruleChainService;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, RuleChain data) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxRuleChains = profileConfiguration.getMaxRuleChains();
+ validateNumberOfEntitiesPerTenant(tenantId, ruleChainDao, maxRuleChains, EntityType.RULE_CHAIN);
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, RuleChain ruleChain) {
+ if (StringUtils.isEmpty(ruleChain.getName())) {
+ throw new DataValidationException("Rule chain name should be specified!");
+ }
+ if (ruleChain.getType() == null) {
+ ruleChain.setType(RuleChainType.CORE);
+ }
+ if (ruleChain.getTenantId() == null || ruleChain.getTenantId().isNullUid()) {
+ throw new DataValidationException("Rule chain should be assigned to tenant!");
+ }
+ Tenant tenant = tenantDao.findById(tenantId, ruleChain.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Rule chain is referencing to non-existent tenant!");
+ }
+ if (ruleChain.isRoot() && RuleChainType.CORE.equals(ruleChain.getType())) {
+ RuleChain rootRuleChain = ruleChainService.getRootTenantRuleChain(ruleChain.getTenantId());
+ if (rootRuleChain != null && !rootRuleChain.getId().equals(ruleChain.getId())) {
+ throw new DataValidationException("Another root rule chain is present in scope of current tenant!");
+ }
+ }
+ if (ruleChain.isRoot() && RuleChainType.EDGE.equals(ruleChain.getType())) {
+ RuleChain edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(ruleChain.getTenantId());
+ if (edgeTemplateRootRuleChain != null && !edgeTemplateRootRuleChain.getId().equals(ruleChain.getId())) {
+ throw new DataValidationException("Another edge template root rule chain is present in scope of current tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java
new file mode 100644
index 0000000000..2c5f6b655e
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.TenantProfile;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.thingsboard.server.dao.tenant.TenantProfileService;
+
+@Component
+public class TenantDataValidator extends DataValidator {
+
+ @Value("${zk.enabled}")
+ private Boolean zkEnabled;
+
+ @Autowired
+ private TenantProfileService tenantProfileService;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, Tenant tenant) {
+ if (StringUtils.isEmpty(tenant.getTitle())) {
+ throw new DataValidationException("Tenant title should be specified!");
+ }
+ if (!StringUtils.isEmpty(tenant.getEmail())) {
+ validateEmail(tenant.getEmail());
+ }
+ validateTenantProfile(tenantId, tenant);
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, Tenant tenant) {
+ Tenant old = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId.getId());
+ if (old == null) {
+ throw new DataValidationException("Can't update non existing tenant!");
+ }
+ validateTenantProfile(tenantId, tenant);
+ }
+
+ private void validateTenantProfile(TenantId tenantId, Tenant tenant) {
+ TenantProfile tenantProfileById = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
+ if (!zkEnabled && (tenantProfileById.isIsolatedTbCore() || tenantProfileById.isIsolatedTbRuleEngine())) {
+ throw new DataValidationException("Can't use isolated tenant profiles in monolith setup!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java
new file mode 100644
index 0000000000..e8207534be
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java
@@ -0,0 +1,69 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.TenantProfile;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantProfileDao;
+import org.thingsboard.server.dao.tenant.TenantProfileService;
+
+@Component
+public class TenantProfileDataValidator extends DataValidator {
+
+ @Autowired
+ private TenantProfileDao tenantProfileDao;
+
+ @Autowired
+ @Lazy
+ private TenantProfileService tenantProfileService;
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, TenantProfile tenantProfile) {
+ if (StringUtils.isEmpty(tenantProfile.getName())) {
+ throw new DataValidationException("Tenant profile name should be specified!");
+ }
+ if (tenantProfile.getProfileData() == null) {
+ throw new DataValidationException("Tenant profile data should be specified!");
+ }
+ if (tenantProfile.getProfileData().getConfiguration() == null) {
+ throw new DataValidationException("Tenant profile data configuration should be specified!");
+ }
+ if (tenantProfile.isDefault()) {
+ TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
+ if (defaultTenantProfile != null && !defaultTenantProfile.getId().equals(tenantProfile.getId())) {
+ throw new DataValidationException("Another default tenant profile is present!");
+ }
+ }
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, TenantProfile tenantProfile) {
+ TenantProfile old = tenantProfileDao.findById(TenantId.SYS_TENANT_ID, tenantProfile.getId().getId());
+ if (old == null) {
+ throw new DataValidationException("Can't update non existing tenant profile!");
+ } else if (old.isIsolatedTbRuleEngine() != tenantProfile.isIsolatedTbRuleEngine()) {
+ throw new DataValidationException("Can't update isolatedTbRuleEngine property!");
+ } else if (old.isIsolatedTbCore() != tenantProfile.isIsolatedTbCore()) {
+ throw new DataValidationException("Can't update isolatedTbCore property!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java
new file mode 100644
index 0000000000..5920ad552f
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.security.UserCredentials;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.exception.IncorrectParameterException;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.user.UserCredentialsDao;
+import org.thingsboard.server.dao.user.UserService;
+
+@Component
+public class UserCredentialsDataValidator extends DataValidator {
+
+ @Autowired
+ private UserCredentialsDao userCredentialsDao;
+
+ @Autowired
+ @Lazy
+ private UserService userService;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, UserCredentials userCredentials) {
+ throw new IncorrectParameterException("Creation of new user credentials is prohibited.");
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, UserCredentials userCredentials) {
+ if (userCredentials.getUserId() == null) {
+ throw new DataValidationException("User credentials should be assigned to user!");
+ }
+ if (userCredentials.isEnabled()) {
+ if (StringUtils.isEmpty(userCredentials.getPassword())) {
+ throw new DataValidationException("Enabled user credentials should have password!");
+ }
+ if (StringUtils.isNotEmpty(userCredentials.getActivateToken())) {
+ throw new DataValidationException("Enabled user credentials can't have activate token!");
+ }
+ }
+ UserCredentials existingUserCredentialsEntity = userCredentialsDao.findById(tenantId, userCredentials.getId().getId());
+ if (existingUserCredentialsEntity == null) {
+ throw new DataValidationException("Unable to update non-existent user credentials!");
+ }
+ User user = userService.findUserById(tenantId, userCredentials.getUserId());
+ if (user == null) {
+ throw new DataValidationException("Can't assign user credentials to non-existent user!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java
new file mode 100644
index 0000000000..94b894acce
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java
@@ -0,0 +1,136 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.dao.customer.CustomerDao;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.thingsboard.server.dao.user.UserDao;
+import org.thingsboard.server.dao.user.UserService;
+
+@Component
+public class UserDataValidator extends DataValidator {
+
+ @Autowired
+ private UserDao userDao;
+
+ @Autowired
+ @Lazy
+ private UserService userService;
+
+ @Autowired
+ private TenantDao tenantDao;
+
+ @Autowired
+ private CustomerDao customerDao;
+
+ @Autowired
+ @Lazy
+ private TbTenantProfileCache tenantProfileCache;
+
+ @Override
+ protected void validateCreate(TenantId tenantId, User user) {
+ if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+ DefaultTenantProfileConfiguration profileConfiguration =
+ (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
+ long maxUsers = profileConfiguration.getMaxUsers();
+ validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER);
+ }
+ }
+
+ @Override
+ protected void validateDataImpl(TenantId requestTenantId, User user) {
+ if (StringUtils.isEmpty(user.getEmail())) {
+ throw new DataValidationException("User email should be specified!");
+ }
+
+ validateEmail(user.getEmail());
+
+ Authority authority = user.getAuthority();
+ if (authority == null) {
+ throw new DataValidationException("User authority isn't defined!");
+ }
+ TenantId tenantId = user.getTenantId();
+ if (tenantId == null) {
+ tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID);
+ user.setTenantId(tenantId);
+ }
+ CustomerId customerId = user.getCustomerId();
+ if (customerId == null) {
+ customerId = new CustomerId(ModelConstants.NULL_UUID);
+ user.setCustomerId(customerId);
+ }
+
+ switch (authority) {
+ case SYS_ADMIN:
+ if (!tenantId.getId().equals(ModelConstants.NULL_UUID)
+ || !customerId.getId().equals(ModelConstants.NULL_UUID)) {
+ throw new DataValidationException("System administrator can't be assigned neither to tenant nor to customer!");
+ }
+ break;
+ case TENANT_ADMIN:
+ if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
+ throw new DataValidationException("Tenant administrator should be assigned to tenant!");
+ } else if (!customerId.getId().equals(ModelConstants.NULL_UUID)) {
+ throw new DataValidationException("Tenant administrator can't be assigned to customer!");
+ }
+ break;
+ case CUSTOMER_USER:
+ if (tenantId.getId().equals(ModelConstants.NULL_UUID)
+ || customerId.getId().equals(ModelConstants.NULL_UUID)) {
+ throw new DataValidationException("Customer user should be assigned to customer!");
+ }
+ break;
+ default:
+ break;
+ }
+
+ User existentUserWithEmail = userService.findUserByEmail(tenantId, user.getEmail());
+ if (existentUserWithEmail != null && !isSameData(existentUserWithEmail, user)) {
+ throw new DataValidationException("User with email '" + user.getEmail() + "' "
+ + " already present in database!");
+ }
+ if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) {
+ Tenant tenant = tenantDao.findById(tenantId, user.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("User is referencing to non-existent tenant!");
+ }
+ }
+ if (!customerId.getId().equals(ModelConstants.NULL_UUID)) {
+ Customer customer = customerDao.findById(tenantId, user.getCustomerId().getId());
+ if (customer == null) {
+ throw new DataValidationException("User is referencing to non-existent customer!");
+ } else if (!customer.getTenantId().getId().equals(tenantId.getId())) {
+ throw new DataValidationException("User can't be assigned to customer from different tenant!");
+ }
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
new file mode 100644
index 0000000000..ec3be14866
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.widget.WidgetType;
+import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.thingsboard.server.dao.widget.WidgetTypeDao;
+import org.thingsboard.server.dao.widget.WidgetsBundleDao;
+
+@Component
+@AllArgsConstructor
+public class WidgetTypeDataValidator extends DataValidator {
+
+ private final WidgetTypeDao widgetTypeDao;
+ private final TenantDao tenantDao;
+ private final WidgetsBundleDao widgetsBundleDao;
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
+ if (StringUtils.isEmpty(widgetTypeDetails.getName())) {
+ throw new DataValidationException("Widgets type name should be specified!");
+ }
+ if (StringUtils.isEmpty(widgetTypeDetails.getBundleAlias())) {
+ throw new DataValidationException("Widgets type bundle alias should be specified!");
+ }
+ if (widgetTypeDetails.getDescriptor() == null || widgetTypeDetails.getDescriptor().size() == 0) {
+ throw new DataValidationException("Widgets type descriptor can't be empty!");
+ }
+ if (widgetTypeDetails.getTenantId() == null) {
+ widgetTypeDetails.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
+ }
+ if (!widgetTypeDetails.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+ Tenant tenant = tenantDao.findById(tenantId, widgetTypeDetails.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Widget type is referencing to non-existent tenant!");
+ }
+ }
+ }
+
+ @Override
+ protected void validateCreate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
+ WidgetsBundle widgetsBundle = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias());
+ if (widgetsBundle == null) {
+ throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!");
+ }
+ String alias = widgetTypeDetails.getAlias();
+ if (alias == null || alias.trim().isEmpty()) {
+ alias = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_");
+ }
+ String originalAlias = alias;
+ int c = 1;
+ WidgetType withSameAlias;
+ do {
+ withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias(), alias);
+ if (withSameAlias != null) {
+ alias = originalAlias + (++c);
+ }
+ } while (withSameAlias != null);
+ widgetTypeDetails.setAlias(alias);
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
+ WidgetType storedWidgetType = widgetTypeDao.findById(tenantId, widgetTypeDetails.getId().getId());
+ if (!storedWidgetType.getTenantId().getId().equals(widgetTypeDetails.getTenantId().getId())) {
+ throw new DataValidationException("Can't move existing widget type to different tenant!");
+ }
+ if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) {
+ throw new DataValidationException("Update of widget type bundle alias is prohibited!");
+ }
+ if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) {
+ throw new DataValidationException("Update of widget type alias is prohibited!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java
new file mode 100644
index 0000000000..d5b1a1149c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service.validator;
+
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.widget.WidgetsBundle;
+import org.thingsboard.server.dao.exception.DataValidationException;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.dao.service.DataValidator;
+import org.thingsboard.server.dao.tenant.TenantDao;
+import org.thingsboard.server.dao.widget.WidgetsBundleDao;
+
+@Component
+@AllArgsConstructor
+public class WidgetsBundleDataValidator extends DataValidator {
+
+ private final WidgetsBundleDao widgetsBundleDao;
+ private final TenantDao tenantDao;
+
+ @Override
+ protected void validateDataImpl(TenantId tenantId, WidgetsBundle widgetsBundle) {
+ if (StringUtils.isEmpty(widgetsBundle.getTitle())) {
+ throw new DataValidationException("Widgets bundle title should be specified!");
+ }
+ if (widgetsBundle.getTenantId() == null) {
+ widgetsBundle.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
+ }
+ if (!widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
+ Tenant tenant = tenantDao.findById(tenantId, widgetsBundle.getTenantId().getId());
+ if (tenant == null) {
+ throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!");
+ }
+ }
+ }
+
+ @Override
+ protected void validateCreate(TenantId tenantId, WidgetsBundle widgetsBundle) {
+ String alias = widgetsBundle.getAlias();
+ if (alias == null || alias.trim().isEmpty()) {
+ alias = widgetsBundle.getTitle().toLowerCase().replaceAll("\\W+", "_");
+ }
+ String originalAlias = alias;
+ int c = 1;
+ WidgetsBundle withSameAlias;
+ do {
+ withSameAlias = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetsBundle.getTenantId().getId(), alias);
+ if (withSameAlias != null) {
+ alias = originalAlias + (++c);
+ }
+ } while (withSameAlias != null);
+ widgetsBundle.setAlias(alias);
+ }
+
+ @Override
+ protected void validateUpdate(TenantId tenantId, WidgetsBundle widgetsBundle) {
+ WidgetsBundle storedWidgetsBundle = widgetsBundleDao.findById(tenantId, widgetsBundle.getId().getId());
+ if (!storedWidgetsBundle.getTenantId().getId().equals(widgetsBundle.getTenantId().getId())) {
+ throw new DataValidationException("Can't move existing widgets bundle to different tenant!");
+ }
+ if (!storedWidgetsBundle.getAlias().equals(widgetsBundle.getAlias())) {
+ throw new DataValidationException("Update of widgets bundle alias is prohibited!");
+ }
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
index fdca0178f5..bf4c71d296 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
@@ -17,13 +17,11 @@ package org.thingsboard.server.dao.settings;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.id.AdminSettingsId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.Validator;
@@ -34,6 +32,9 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
@Autowired
private AdminSettingsDao adminSettingsDao;
+ @Autowired
+ private DataValidator adminSettingsValidator;
+
@Override
public AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId) {
log.trace("Executing findAdminSettingsById [{}]", adminSettingsId);
@@ -62,37 +63,4 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
return adminSettingsDao.save(tenantId, adminSettings);
}
- private DataValidator adminSettingsValidator =
- new DataValidator() {
-
- @Override
- protected void validateCreate(TenantId tenantId, AdminSettings adminSettings) {
- AdminSettings existentAdminSettingsWithKey = findAdminSettingsByKey(tenantId, adminSettings.getKey());
- if (existentAdminSettingsWithKey != null) {
- throw new DataValidationException("Admin settings with such name already exists!");
- }
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, AdminSettings adminSettings) {
- AdminSettings existentAdminSettings = findAdminSettingsById(tenantId, adminSettings.getId());
- if (existentAdminSettings != null) {
- if (!existentAdminSettings.getKey().equals(adminSettings.getKey())) {
- throw new DataValidationException("Changing key of admin settings entry is prohibited!");
- }
- }
- }
-
-
- @Override
- protected void validateDataImpl(TenantId tenantId, AdminSettings adminSettings) {
- if (StringUtils.isEmpty(adminSettings.getKey())) {
- throw new DataValidationException("Key should be specified!");
- }
- if (adminSettings.getJsonValue() == null) {
- throw new DataValidationException("Json value should be specified!");
- }
- }
- };
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
index a4f9c65807..0976c49410 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
@@ -16,7 +16,6 @@
package org.thingsboard.server.dao.tenant;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
@@ -55,6 +54,9 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
@Autowired
private CacheManager cacheManager;
+ @Autowired
+ private DataValidator tenantProfileValidator;
+
@Cacheable(cacheNames = TENANT_PROFILE_CACHE, key = "{#tenantProfileId.id}")
@Override
public TenantProfile findTenantProfileById(TenantId tenantId, TenantProfileId tenantProfileId) {
@@ -104,10 +106,10 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
if (tenantProfile != null && tenantProfile.isDefault()) {
throw new DataValidationException("Deletion of Default Tenant Profile is prohibited!");
}
- this.removeTenantProfile(tenantId, tenantProfileId);
+ this.removeTenantProfile(tenantId, tenantProfileId, false);
}
- private void removeTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId) {
+ private void removeTenantProfile(TenantId tenantId, TenantProfileId tenantProfileId, boolean isDefault) {
try {
tenantProfileDao.removeById(tenantId, tenantProfileId.getId());
} catch (Exception t) {
@@ -122,6 +124,10 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
Cache cache = cacheManager.getCache(TENANT_PROFILE_CACHE);
cache.evict(Collections.singletonList(tenantProfileId.getId()));
cache.evict(Arrays.asList("info", tenantProfileId.getId()));
+ if (isDefault) {
+ cache.evict(Collections.singletonList("default"));
+ cache.evict(Arrays.asList("default", "info"));
+ }
}
@Override
@@ -209,41 +215,7 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
tenantProfilesRemover.removeEntities(tenantId, null);
}
- private DataValidator tenantProfileValidator =
- new DataValidator() {
- @Override
- protected void validateDataImpl(TenantId tenantId, TenantProfile tenantProfile) {
- if (StringUtils.isEmpty(tenantProfile.getName())) {
- throw new DataValidationException("Tenant profile name should be specified!");
- }
- if (tenantProfile.getProfileData() == null) {
- throw new DataValidationException("Tenant profile data should be specified!");
- }
- if (tenantProfile.getProfileData().getConfiguration() == null) {
- throw new DataValidationException("Tenant profile data configuration should be specified!");
- }
- if (tenantProfile.isDefault()) {
- TenantProfile defaultTenantProfile = findDefaultTenantProfile(tenantId);
- if (defaultTenantProfile != null && !defaultTenantProfile.getId().equals(tenantProfile.getId())) {
- throw new DataValidationException("Another default tenant profile is present!");
- }
- }
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, TenantProfile tenantProfile) {
- TenantProfile old = tenantProfileDao.findById(TenantId.SYS_TENANT_ID, tenantProfile.getId().getId());
- if (old == null) {
- throw new DataValidationException("Can't update non existing tenant profile!");
- } else if (old.isIsolatedTbRuleEngine() != tenantProfile.isIsolatedTbRuleEngine()) {
- throw new DataValidationException("Can't update isolatedTbRuleEngine property!");
- } else if (old.isIsolatedTbCore() != tenantProfile.isIsolatedTbCore()) {
- throw new DataValidationException("Can't update isolatedTbCore property!");
- }
- }
- };
-
- private PaginatedRemover tenantProfilesRemover =
+ private final PaginatedRemover tenantProfilesRemover =
new PaginatedRemover() {
@Override
@@ -253,7 +225,7 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
@Override
protected void removeEntity(TenantId tenantId, TenantProfile entity) {
- removeTenantProfile(tenantId, entity.getId());
+ removeTenantProfile(tenantId, entity.getId(), entity.isDefault());
}
};
diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
index a8a1dc6e5b..6488c898da 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
@@ -17,9 +17,7 @@ package org.thingsboard.server.dao.tenant;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo;
@@ -34,7 +32,6 @@ import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService;
@@ -55,9 +52,6 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
private static final String DEFAULT_TENANT_REGION = "Global";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
- @Value("${zk.enabled}")
- private Boolean zkEnabled;
-
@Autowired
private TenantDao tenantDao;
@@ -103,6 +97,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired
private RpcService rpcService;
+ @Autowired
+ private DataValidator tenantValidator;
+
@Override
public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", tenantId);
@@ -183,36 +180,6 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID);
}
- private DataValidator tenantValidator =
- new DataValidator() {
- @Override
- protected void validateDataImpl(TenantId tenantId, Tenant tenant) {
- if (StringUtils.isEmpty(tenant.getTitle())) {
- throw new DataValidationException("Tenant title should be specified!");
- }
- if (!StringUtils.isEmpty(tenant.getEmail())) {
- validateEmail(tenant.getEmail());
- }
- validateTenantProfile(tenantId, tenant);
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, Tenant tenant) {
- Tenant old = tenantDao.findById(TenantId.SYS_TENANT_ID, tenantId.getId());
- if (old == null) {
- throw new DataValidationException("Can't update non existing tenant!");
- }
- validateTenantProfile(tenantId, tenant);
- }
-
- private void validateTenantProfile(TenantId tenantId, Tenant tenant) {
- TenantProfile tenantProfileById = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
- if (!zkEnabled && (tenantProfileById.isIsolatedTbCore() || tenantProfileById.isIsolatedTbRuleEngine())) {
- throw new DataValidationException("Can't use isolated tenant profiles in monolith setup!");
- }
- }
- };
-
private PaginatedRemover tenantsRemover =
new PaginatedRemover<>() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
index 5b75342420..2193305837 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.usagerecord;
+import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ApiFeature;
@@ -33,7 +34,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantProfileDao;
@@ -47,6 +47,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service
@Slf4j
+@AllArgsConstructor
public class ApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@@ -54,13 +55,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
private final TenantProfileDao tenantProfileDao;
private final TenantDao tenantDao;
private final TimeseriesService tsService;
-
- public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao, TimeseriesService tsService) {
- this.tenantDao = tenantDao;
- this.apiUsageStateDao = apiUsageStateDao;
- this.tenantProfileDao = tenantProfileDao;
- this.tsService = tsService;
- }
+ private final DataValidator apiUsageStateValidator;
@Override
public void deleteApiUsageStateByTenantId(TenantId tenantId) {
@@ -157,24 +152,4 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
return apiUsageStateDao.findById(tenantId, id.getId());
}
- private DataValidator apiUsageStateValidator =
- new DataValidator() {
- @Override
- protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) {
- if (apiUsageState.getTenantId() == null) {
- throw new DataValidationException("ApiUsageState should be assigned to tenant!");
- } else {
- Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId());
- if (tenant == null && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) {
- throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!");
- }
- }
- if (apiUsageState.getEntityId() == null) {
- throw new DataValidationException("UsageRecord should be assigned to entity!");
- } else if (apiUsageState.getEntityId().getEntityType() != EntityType.TENANT && apiUsageState.getEntityId().getEntityType() != EntityType.CUSTOMER) {
- throw new DataValidationException("Only Tenant and Customer Usage Records are supported!");
- }
- }
- };
-
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
index d48520648e..ba0f09d64e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
@@ -19,19 +19,15 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
+import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
-import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.EntityType;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -39,19 +35,12 @@ import org.thingsboard.server.common.data.id.UserCredentialsId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
-import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent;
-import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
-import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
-import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
-import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
-import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.HashMap;
import java.util.Map;
@@ -80,22 +69,19 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
private final UserDao userDao;
private final UserCredentialsDao userCredentialsDao;
- private final TenantDao tenantDao;
- private final CustomerDao customerDao;
- private final TbTenantProfileCache tenantProfileCache;
+ private final DataValidator userValidator;
+ private final DataValidator userCredentialsValidator;
private final ApplicationEventPublisher eventPublisher;
public UserServiceImpl(UserDao userDao,
UserCredentialsDao userCredentialsDao,
- TenantDao tenantDao,
- CustomerDao customerDao,
- @Lazy TbTenantProfileCache tenantProfileCache,
+ DataValidator userValidator,
+ DataValidator userCredentialsValidator,
ApplicationEventPublisher eventPublisher) {
this.userDao = userDao;
this.userCredentialsDao = userCredentialsDao;
- this.tenantDao = tenantDao;
- this.customerDao = customerDao;
- this.tenantProfileCache = tenantProfileCache;
+ this.userValidator = userValidator;
+ this.userCredentialsValidator = userCredentialsValidator;
this.eventPublisher = eventPublisher;
}
@@ -383,119 +369,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
saveUser(user);
}
- private final DataValidator userValidator =
- new DataValidator<>() {
- @Override
- protected void validateCreate(TenantId tenantId, User user) {
- if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
- DefaultTenantProfileConfiguration profileConfiguration =
- (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
- long maxUsers = profileConfiguration.getMaxUsers();
- validateNumberOfEntitiesPerTenant(tenantId, userDao, maxUsers, EntityType.USER);
- }
- }
-
- @Override
- protected void validateDataImpl(TenantId requestTenantId, User user) {
- if (StringUtils.isEmpty(user.getEmail())) {
- throw new DataValidationException("User email should be specified!");
- }
-
- validateEmail(user.getEmail());
-
- Authority authority = user.getAuthority();
- if (authority == null) {
- throw new DataValidationException("User authority isn't defined!");
- }
- TenantId tenantId = user.getTenantId();
- if (tenantId == null) {
- tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID);
- user.setTenantId(tenantId);
- }
- CustomerId customerId = user.getCustomerId();
- if (customerId == null) {
- customerId = new CustomerId(ModelConstants.NULL_UUID);
- user.setCustomerId(customerId);
- }
-
- switch (authority) {
- case SYS_ADMIN:
- if (!tenantId.getId().equals(ModelConstants.NULL_UUID)
- || !customerId.getId().equals(ModelConstants.NULL_UUID)) {
- throw new DataValidationException("System administrator can't be assigned neither to tenant nor to customer!");
- }
- break;
- case TENANT_ADMIN:
- if (tenantId.getId().equals(ModelConstants.NULL_UUID)) {
- throw new DataValidationException("Tenant administrator should be assigned to tenant!");
- } else if (!customerId.getId().equals(ModelConstants.NULL_UUID)) {
- throw new DataValidationException("Tenant administrator can't be assigned to customer!");
- }
- break;
- case CUSTOMER_USER:
- if (tenantId.getId().equals(ModelConstants.NULL_UUID)
- || customerId.getId().equals(ModelConstants.NULL_UUID)) {
- throw new DataValidationException("Customer user should be assigned to customer!");
- }
- break;
- default:
- break;
- }
-
- User existentUserWithEmail = findUserByEmail(tenantId, user.getEmail());
- if (existentUserWithEmail != null && !isSameData(existentUserWithEmail, user)) {
- throw new DataValidationException("User with email '" + user.getEmail() + "' "
- + " already present in database!");
- }
- if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) {
- Tenant tenant = tenantDao.findById(tenantId, user.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("User is referencing to non-existent tenant!");
- }
- }
- if (!customerId.getId().equals(ModelConstants.NULL_UUID)) {
- Customer customer = customerDao.findById(tenantId, user.getCustomerId().getId());
- if (customer == null) {
- throw new DataValidationException("User is referencing to non-existent customer!");
- } else if (!customer.getTenantId().getId().equals(tenantId.getId())) {
- throw new DataValidationException("User can't be assigned to customer from different tenant!");
- }
- }
- }
- };
-
- private final DataValidator userCredentialsValidator =
- new DataValidator<>() {
-
- @Override
- protected void validateCreate(TenantId tenantId, UserCredentials userCredentials) {
- throw new IncorrectParameterException("Creation of new user credentials is prohibited.");
- }
-
- @Override
- protected void validateDataImpl(TenantId tenantId, UserCredentials userCredentials) {
- if (userCredentials.getUserId() == null) {
- throw new DataValidationException("User credentials should be assigned to user!");
- }
- if (userCredentials.isEnabled()) {
- if (StringUtils.isEmpty(userCredentials.getPassword())) {
- throw new DataValidationException("Enabled user credentials should have password!");
- }
- if (StringUtils.isNotEmpty(userCredentials.getActivateToken())) {
- throw new DataValidationException("Enabled user credentials can't have activate token!");
- }
- }
- UserCredentials existingUserCredentialsEntity = userCredentialsDao.findById(tenantId, userCredentials.getId().getId());
- if (existingUserCredentialsEntity == null) {
- throw new DataValidationException("Unable to update non-existent user credentials!");
- }
- User user = findUserById(tenantId, userCredentials.getUserId());
- if (user == null) {
- throw new DataValidationException("Can't assign user credentials to non-existent user!");
- }
- }
- };
-
private final PaginatedRemover tenantAdminsRemover = new PaginatedRemover<>() {
@Override
protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
index 4defd1b34d..9b64dc0d68 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
@@ -16,21 +16,15 @@
package org.thingsboard.server.dao.widget;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
-import org.thingsboard.server.common.data.widget.WidgetsBundle;
-import org.thingsboard.server.dao.exception.DataValidationException;
-import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.List;
@@ -44,10 +38,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
private WidgetTypeDao widgetTypeDao;
@Autowired
- private TenantDao tenantDao;
-
- @Autowired
- private WidgetsBundleDao widgetsBundleService;
+ private DataValidator widgetTypeValidator;
@Override
public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) {
@@ -121,64 +112,4 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
}
}
- private DataValidator widgetTypeValidator =
- new DataValidator<>() {
- @Override
- protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
- if (StringUtils.isEmpty(widgetTypeDetails.getName())) {
- throw new DataValidationException("Widgets type name should be specified!");
- }
- if (StringUtils.isEmpty(widgetTypeDetails.getBundleAlias())) {
- throw new DataValidationException("Widgets type bundle alias should be specified!");
- }
- if (widgetTypeDetails.getDescriptor() == null || widgetTypeDetails.getDescriptor().size() == 0) {
- throw new DataValidationException("Widgets type descriptor can't be empty!");
- }
- if (widgetTypeDetails.getTenantId() == null) {
- widgetTypeDetails.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
- }
- if (!widgetTypeDetails.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
- Tenant tenant = tenantDao.findById(tenantId, widgetTypeDetails.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Widget type is referencing to non-existent tenant!");
- }
- }
- }
-
- @Override
- protected void validateCreate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
- WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias());
- if (widgetsBundle == null) {
- throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!");
- }
- String alias = widgetTypeDetails.getAlias();
- if (alias == null || alias.trim().isEmpty()) {
- alias = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_");
- }
- String originalAlias = alias;
- int c = 1;
- WidgetType withSameAlias;
- do {
- withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias(), alias);
- if (withSameAlias != null) {
- alias = originalAlias + (++c);
- }
- } while (withSameAlias != null);
- widgetTypeDetails.setAlias(alias);
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) {
- WidgetType storedWidgetType = widgetTypeDao.findById(tenantId, widgetTypeDetails.getId().getId());
- if (!storedWidgetType.getTenantId().getId().equals(widgetTypeDetails.getTenantId().getId())) {
- throw new DataValidationException("Can't move existing widget type to different tenant!");
- }
- if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) {
- throw new DataValidationException("Update of widget type bundle alias is prohibited!");
- }
- if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) {
- throw new DataValidationException("Update of widget type alias is prohibited!");
- }
- }
- };
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
index 96e18abd48..df5e4a29f1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
@@ -16,22 +16,17 @@
package org.thingsboard.server.dao.widget;
import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
-import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
-import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
-import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
-import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList;
import java.util.List;
@@ -48,10 +43,10 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
private WidgetsBundleDao widgetsBundleDao;
@Autowired
- private TenantDao tenantDao;
+ private WidgetTypeService widgetTypeService;
@Autowired
- private WidgetTypeService widgetTypeService;
+ private DataValidator widgetsBundleValidator;
@Override
public WidgetsBundle findWidgetsBundleById(TenantId tenantId, WidgetsBundleId widgetsBundleId) {
@@ -150,56 +145,6 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
tenantWidgetsBundleRemover.removeEntities(tenantId, tenantId);
}
- private DataValidator widgetsBundleValidator =
- new DataValidator() {
-
- @Override
- protected void validateDataImpl(TenantId tenantId, WidgetsBundle widgetsBundle) {
- if (StringUtils.isEmpty(widgetsBundle.getTitle())) {
- throw new DataValidationException("Widgets bundle title should be specified!");
- }
- if (widgetsBundle.getTenantId() == null) {
- widgetsBundle.setTenantId(TenantId.fromUUID(ModelConstants.NULL_UUID));
- }
- if (!widgetsBundle.getTenantId().getId().equals(ModelConstants.NULL_UUID)) {
- Tenant tenant = tenantDao.findById(tenantId, widgetsBundle.getTenantId().getId());
- if (tenant == null) {
- throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!");
- }
- }
- }
-
- @Override
- protected void validateCreate(TenantId tenantId, WidgetsBundle widgetsBundle) {
- String alias = widgetsBundle.getAlias();
- if (alias == null || alias.trim().isEmpty()) {
- alias = widgetsBundle.getTitle().toLowerCase().replaceAll("\\W+", "_");
- }
- String originalAlias = alias;
- int c = 1;
- WidgetsBundle withSameAlias;
- do {
- withSameAlias = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetsBundle.getTenantId().getId(), alias);
- if (withSameAlias != null) {
- alias = originalAlias + (++c);
- }
- } while(withSameAlias != null);
- widgetsBundle.setAlias(alias);
- }
-
- @Override
- protected void validateUpdate(TenantId tenantId, WidgetsBundle widgetsBundle) {
- WidgetsBundle storedWidgetsBundle = widgetsBundleDao.findById(tenantId, widgetsBundle.getId().getId());
- if (!storedWidgetsBundle.getTenantId().getId().equals(widgetsBundle.getTenantId().getId())) {
- throw new DataValidationException("Can't move existing widgets bundle to different tenant!");
- }
- if (!storedWidgetsBundle.getAlias().equals(widgetsBundle.getAlias())) {
- throw new DataValidationException("Update of widgets bundle alias is prohibited!");
- }
- }
-
- };
-
private PaginatedRemover tenantWidgetsBundleRemover =
new PaginatedRemover() {
diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql
index b73977885d..4e2596485b 100644
--- a/dao/src/main/resources/sql/schema-entities.sql
+++ b/dao/src/main/resources/sql/schema-entities.sql
@@ -213,7 +213,6 @@ CREATE TABLE IF NOT EXISTS ota_package (
additional_info varchar,
search_text varchar(255),
CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
--- CONSTRAINT fk_device_profile_firmware FOREIGN KEY (device_profile_id) REFERENCES device_profile(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS device_profile (
@@ -243,6 +242,11 @@ CREATE TABLE IF NOT EXISTS device_profile (
CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id)
);
+ALTER TABLE ota_package
+ ADD CONSTRAINT fk_device_profile_ota_package
+ FOREIGN KEY (device_profile_id) REFERENCES device_profile (id)
+ ON DELETE CASCADE;
+
-- We will use one-to-many relation in the first release and extend this feature in case of user requests
-- CREATE TABLE IF NOT EXISTS device_profile_firmware (
-- device_profile_id uuid NOT NULL,
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 34e2618ff6..c1ecafda6e 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
@@ -33,14 +33,18 @@ import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Event;
+import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
+import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.audit.AuditLogLevelFilter;
@@ -70,6 +74,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
@@ -251,4 +256,21 @@ public abstract class AbstractServiceTest {
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
return edge;
}
+
+ protected OtaPackage constructDefaultOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId) {
+ OtaPackage firmware = new OtaPackage();
+ firmware.setTenantId(tenantId);
+ firmware.setDeviceProfileId(deviceProfileId);
+ firmware.setType(OtaPackageType.FIRMWARE);
+ firmware.setTitle("My firmware");
+ firmware.setVersion("3.3.3");
+ firmware.setFileName("filename.txt");
+ firmware.setContentType("text/plain");
+ firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256);
+ firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a");
+ firmware.setData(ByteBuffer.wrap(new byte[]{1}));
+ firmware.setDataSize(1L);
+ return firmware;
+ }
+
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
index 85951fad5a..770b215adf 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
@@ -418,7 +418,7 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
edge.setType("default");
edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
try {
dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId());
} finally {
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java
index 8d683abfb8..689752f0fe 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java
@@ -49,6 +49,9 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
@Autowired
private DeviceCredentialsService deviceCredentialsService;
+ @Autowired
+ private DataValidator credentialsValidator;
+
private DeviceCredentialsDao deviceCredentialsDao;
private DeviceService deviceService;
@@ -59,10 +62,15 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
@Before
public void setup() throws Exception {
- deviceCredentialsDao = mock(DeviceCredentialsDao.class);
deviceService = mock(DeviceService.class);
+ deviceCredentialsDao = mock(DeviceCredentialsDao.class);
+
+ ReflectionTestUtils.setField(credentialsValidator, "deviceService", deviceService);
+ ReflectionTestUtils.setField(credentialsValidator, "deviceCredentialsDao", deviceCredentialsDao);
+
ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "deviceCredentialsDao", deviceCredentialsDao);
- ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "deviceService", deviceService);
+ ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "credentialsValidator", credentialsValidator);
+
}
@After
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
index fae4aeea6a..4e0b0e7dfc 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
@@ -45,6 +45,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
public abstract class BaseDeviceProfileServiceTest extends AbstractServiceTest {
@@ -252,6 +253,22 @@ public abstract class BaseDeviceProfileServiceTest extends AbstractServiceTest {
deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId());
}
+ @Test
+ public void testDeleteDeviceProfileWithExistingOta_cascadeDelete() {
+ DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
+ deviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile);
+ OtaPackage otaPackage = constructDefaultOtaPackage(tenantId, deviceProfile.getId());
+ otaPackage = otaPackageService.saveOtaPackage(otaPackage);
+
+ assertThat(deviceProfileService.findDeviceProfileById(tenantId, deviceProfile.getId())).isNotNull();
+ assertThat(otaPackageService.findOtaPackageById(tenantId, otaPackage.getId())).isNotNull();
+
+ deviceProfileService.deleteDeviceProfile(tenantId, deviceProfile.getId());
+
+ assertThat(deviceProfileService.findDeviceProfileById(tenantId, deviceProfile.getId())).isNull();
+ assertThat(otaPackageService.findOtaPackageById(tenantId, otaPackage.getId())).isNull();
+ }
+
@Test
public void testDeleteDeviceProfile() {
DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile");
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java
index dcf8957b0e..0bbbf82452 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java
@@ -60,7 +60,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
@Test
public void testSaveEdge() {
Edge edge = constructEdge("My edge", "default");
- Edge savedEdge = edgeService.saveEdge(edge, true);
+ Edge savedEdge = edgeService.saveEdge(edge);
Assert.assertNotNull(savedEdge);
Assert.assertNotNull(savedEdge.getId());
@@ -72,7 +72,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
savedEdge.setName("My new edge");
- edgeService.saveEdge(savedEdge, true);
+ edgeService.saveEdge(savedEdge);
Edge foundEdge = edgeService.findEdgeById(tenantId, savedEdge.getId());
Assert.assertEquals(foundEdge.getName(), savedEdge.getName());
@@ -84,7 +84,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
Edge edge = new Edge();
edge.setType("default");
edge.setTenantId(tenantId);
- edgeService.saveEdge(edge, true);
+ edgeService.saveEdge(edge);
}
@Test(expected = DataValidationException.class)
@@ -92,7 +92,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
Edge edge = new Edge();
edge.setName("My edge");
edge.setType("default");
- edgeService.saveEdge(edge, true);
+ edgeService.saveEdge(edge);
}
@Test(expected = DataValidationException.class)
@@ -101,13 +101,13 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
edge.setName("My edge");
edge.setType("default");
edge.setTenantId(TenantId.fromUUID(Uuids.timeBased()));
- edgeService.saveEdge(edge, true);
+ edgeService.saveEdge(edge);
}
@Test(expected = DataValidationException.class)
public void testAssignEdgeToNonExistentCustomer() {
Edge edge = constructEdge("My edge", "default");
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
try {
edgeService.assignEdgeToCustomer(tenantId, edge.getId(), new CustomerId(Uuids.timeBased()));
} finally {
@@ -118,7 +118,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class)
public void testAssignEdgeToCustomerFromDifferentTenant() {
Edge edge = constructEdge("My edge", "default");
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
Tenant tenant = new Tenant();
tenant.setTitle("Test different tenant");
tenant = tenantService.saveTenant(tenant);
@@ -137,7 +137,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
@Test
public void testFindEdgeById() {
Edge edge = constructEdge("My edge", "default");
- Edge savedEdge = edgeService.saveEdge(edge, true);
+ Edge savedEdge = edgeService.saveEdge(edge);
Edge foundEdge = edgeService.findEdgeById(tenantId, savedEdge.getId());
Assert.assertNotNull(foundEdge);
Assert.assertEquals(savedEdge, foundEdge);
@@ -150,15 +150,15 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
try {
for (int i = 0; i < 3; i++) {
Edge edge = constructEdge("My edge B" + i, "typeB");
- edges.add(edgeService.saveEdge(edge, true));
+ edges.add(edgeService.saveEdge(edge));
}
for (int i = 0; i < 7; i++) {
Edge edge = constructEdge("My edge C" + i, "typeC");
- edges.add(edgeService.saveEdge(edge, true));
+ edges.add(edgeService.saveEdge(edge));
}
for (int i = 0; i < 9; i++) {
Edge edge = constructEdge("My edge A" + i, "typeA");
- edges.add(edgeService.saveEdge(edge, true));
+ edges.add(edgeService.saveEdge(edge));
}
List edgeTypes = edgeService.findEdgeTypesByTenantId(tenantId).get();
Assert.assertNotNull(edgeTypes);
@@ -176,7 +176,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
@Test
public void testDeleteEdge() {
Edge edge = constructEdge("My edge", "default");
- Edge savedEdge = edgeService.saveEdge(edge, true);
+ Edge savedEdge = edgeService.saveEdge(edge);
Edge foundEdge = edgeService.findEdgeById(tenantId, savedEdge.getId());
Assert.assertNotNull(foundEdge);
edgeService.deleteEdge(tenantId, savedEdge.getId());
@@ -195,7 +195,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
List edges = new ArrayList<>();
for (int i = 0; i < 178; i++) {
Edge edge = constructEdge(tenantId, "Edge " + i, "default");
- edges.add(edgeService.saveEdge(edge, true));
+ edges.add(edgeService.saveEdge(edge));
}
List loadedEdges = new ArrayList<>();
@@ -233,7 +233,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, "default");
- edgesTitle1.add(edgeService.saveEdge(edge, true));
+ edgesTitle1.add(edgeService.saveEdge(edge));
}
String title2 = "Edge title 2";
List edgesTitle2 = new ArrayList<>();
@@ -242,7 +242,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, "default");
- edgesTitle2.add(edgeService.saveEdge(edge, true));
+ edgesTitle2.add(edgeService.saveEdge(edge));
}
List loadedEdgesTitle1 = new ArrayList<>();
@@ -305,7 +305,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, type1);
- edgesType1.add(edgeService.saveEdge(edge, true));
+ edgesType1.add(edgeService.saveEdge(edge));
}
String title2 = "Edge title 2";
String type2 = "typeB";
@@ -315,7 +315,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, type2);
- edgesType2.add(edgeService.saveEdge(edge, true));
+ edgesType2.add(edgeService.saveEdge(edge));
}
List loadedEdgesType1 = new ArrayList<>();
@@ -385,7 +385,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
List edges = new ArrayList<>();
for (int i = 0; i < 278; i++) {
Edge edge = constructEdge(tenantId, "Edge" + i, "default");
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
edges.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId));
}
@@ -431,7 +431,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, "default");
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
edgesTitle1.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId));
}
String title2 = "Edge title 2";
@@ -441,7 +441,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, "default");
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
edgesTitle2.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId));
}
@@ -513,7 +513,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title1 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, type1);
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
edgesType1.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId));
}
String title2 = "Edge title 2";
@@ -524,7 +524,7 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String name = title2 + suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
Edge edge = constructEdge(name, type2);
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
edgesType2.add(edgeService.assignEdgeToCustomer(tenantId, edge.getId(), customerId));
}
@@ -589,12 +589,12 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest {
String edgeNameAfterRename = RandomStringUtils.randomAlphanumeric(15);
Edge edge = constructEdge(tenantId, edgeNameBeforeRename, "default");
- edgeService.saveEdge(edge, true);
+ edgeService.saveEdge(edge);
Edge savedEdge = edgeService.findEdgeByTenantIdAndName(tenantId, edgeNameBeforeRename);
savedEdge.setName(edgeNameAfterRename);
- edgeService.saveEdge(savedEdge, true);
+ edgeService.saveEdge(savedEdge);
Edge renamedEdge = edgeService.findEdgeByTenantIdAndName(tenantId, edgeNameBeforeRename);
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
index 820b500b8c..430ac85391 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
@@ -21,8 +21,8 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
-import org.hamcrest.Matchers;
import org.apache.commons.lang3.StringUtils;
+import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -91,8 +91,8 @@ import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
@Slf4j
public abstract class BaseEntityServiceTest extends AbstractServiceTest {
@@ -241,7 +241,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
List edges = new ArrayList<>();
for (int i = 0; i < 97; i++) {
Edge edge = createEdge(i, "default");
- edges.add(edgeService.saveEdge(edge, true));
+ edges.add(edgeService.saveEdge(edge));
}
EdgeTypeFilter filter = new EdgeTypeFilter();
@@ -279,7 +279,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
public void testCountHierarchicalEntitiesByEdgeSearchQuery() throws InterruptedException {
for (int i = 0; i < 5; i++) {
Edge edge = createEdge(i, "type" + i);
- edge = edgeService.saveEdge(edge, true);
+ edge = edgeService.saveEdge(edge);
//TO make sure devices have different created time
Thread.sleep(1);
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
index d49594a955..7dedf14bab 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
@@ -488,7 +488,6 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId());
} finally {
deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId());
- otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId());
}
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java
index 861b383e8e..92a3a6af3a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java
@@ -495,7 +495,7 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest {
@Test
public void testFindEdgeRuleChainsByTenantIdAndName() {
Edge edge = constructEdge(tenantId, "My edge", "default");
- Edge savedEdge = edgeService.saveEdge(edge, true);
+ Edge savedEdge = edgeService.saveEdge(edge);
String name1 = "Edge RuleChain name 1";
List ruleChainsName1 = new ArrayList<>();
diff --git a/dao/src/test/resources/sql/system-test-psql.sql b/dao/src/test/resources/sql/system-test-psql.sql
index 16dcb8c3ae..177f838bb1 100644
--- a/dao/src/test/resources/sql/system-test-psql.sql
+++ b/dao/src/test/resources/sql/system-test-psql.sql
@@ -1,2 +1,2 @@
--PostgreSQL specific truncate to fit constraints
-TRUNCATE TABLE device_credentials, device, device_profile, rule_node_state, rule_node, rule_chain;
\ No newline at end of file
+TRUNCATE TABLE device_credentials, device, device_profile, ota_package, rule_node_state, rule_node, rule_chain;
\ No newline at end of file
diff --git a/dao/src/test/resources/sql/system-test.sql b/dao/src/test/resources/sql/system-test.sql
index 68a0e0efa3..d673548fb0 100644
--- a/dao/src/test/resources/sql/system-test.sql
+++ b/dao/src/test/resources/sql/system-test.sql
@@ -1,6 +1,6 @@
TRUNCATE TABLE device_credentials;
TRUNCATE TABLE device;
-TRUNCATE TABLE device_profile;
+TRUNCATE TABLE device_profile CASCADE;
TRUNCATE TABLE rule_node_state;
TRUNCATE TABLE rule_node;
TRUNCATE TABLE rule_chain;
\ No newline at end of file
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
index b67662dfe7..707aa53cd2 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
@@ -23,6 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.EnumUtils;
+import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
@@ -122,40 +123,66 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode createNewAlarm(TbContext ctx, TbMsg msg, Alarm msgAlarm) {
- ListenableFuture asyncAlarm;
- if (msgAlarm != null) {
- asyncAlarm = Futures.immediateFuture(msgAlarm);
- } else {
+ ListenableFuture asyncDetails;
+ boolean buildDetails = !config.isUseMessageAlarmData() || config.isOverwriteAlarmDetails();
+ if (buildDetails) {
ctx.logJsEvalRequest();
- asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null),
- details -> {
- ctx.logJsEvalResponse();
- return buildAlarm(msg, details, ctx.getTenantId());
- }, MoreExecutors.directExecutor());
+ asyncDetails = buildAlarmDetails(ctx, msg, null);
+ } else {
+ asyncDetails = Futures.immediateFuture(null);
}
+ ListenableFuture asyncAlarm = Futures.transform(asyncDetails, details -> {
+ if (buildDetails) {
+ ctx.logJsEvalResponse();
+ }
+ Alarm newAlarm;
+ if (msgAlarm != null) {
+ newAlarm = msgAlarm;
+ if (buildDetails) {
+ newAlarm.setDetails(details);
+ }
+ } else {
+ newAlarm = buildAlarm(msg, details, ctx.getTenantId());
+ }
+ return newAlarm;
+ }, MoreExecutors.directExecutor());
ListenableFuture asyncCreated = Futures.transform(asyncAlarm,
alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor());
return Futures.transform(asyncCreated, alarm -> new TbAlarmResult(true, false, false, alarm), MoreExecutors.directExecutor());
}
private ListenableFuture updateAlarm(TbContext ctx, TbMsg msg, Alarm existingAlarm, Alarm msgAlarm) {
- ctx.logJsEvalRequest();
- ListenableFuture asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, existingAlarm.getDetails()), (Function) details -> {
- ctx.logJsEvalResponse();
+ ListenableFuture asyncDetails;
+ boolean buildDetails = !config.isUseMessageAlarmData() || config.isOverwriteAlarmDetails();
+ if (buildDetails) {
+ ctx.logJsEvalRequest();
+ asyncDetails = buildAlarmDetails(ctx, msg, existingAlarm.getDetails());
+ } else {
+ asyncDetails = Futures.immediateFuture(null);
+ }
+ ListenableFuture asyncUpdated = Futures.transform(asyncDetails, (Function) details -> {
+ if (buildDetails) {
+ ctx.logJsEvalResponse();
+ }
if (msgAlarm != null) {
existingAlarm.setSeverity(msgAlarm.getSeverity());
existingAlarm.setPropagate(msgAlarm.isPropagate());
existingAlarm.setPropagateToOwner(msgAlarm.isPropagateToOwner());
existingAlarm.setPropagateToTenant(msgAlarm.isPropagateToTenant());
existingAlarm.setPropagateRelationTypes(msgAlarm.getPropagateRelationTypes());
+ if (buildDetails) {
+ existingAlarm.setDetails(details);
+ } else {
+ existingAlarm.setDetails(msgAlarm.getDetails());
+ }
} else {
existingAlarm.setSeverity(processAlarmSeverity(msg));
existingAlarm.setPropagate(config.isPropagate());
existingAlarm.setPropagateToOwner(config.isPropagateToOwner());
existingAlarm.setPropagateToTenant(config.isPropagateToTenant());
existingAlarm.setPropagateRelationTypes(relationTypes);
+ existingAlarm.setDetails(details);
}
- existingAlarm.setDetails(details);
existingAlarm.setEndTs(System.currentTimeMillis());
return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm);
}, ctx.getDbCallbackExecutor());
@@ -171,6 +198,8 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode relationTypes;
@@ -44,8 +45,10 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
configuration.setPropagateToOwner(false);
configuration.setPropagateToTenant(false);
configuration.setUseMessageAlarmData(false);
+ configuration.setOverwriteAlarmDetails(false);
configuration.setRelationTypes(Collections.emptyList());
configuration.setDynamicSeverity(false);
return configuration;
}
+
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java
index 837b5272ec..9b9974cbb9 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java
@@ -99,7 +99,7 @@ public class TbDeviceProfileNode implements TbNode {
log.info("[{}] Fetched alarm rule state for {} entities", ctx.getSelfId(), fetchCount);
}
if (!config.isPersistAlarmRulesState() && ctx.isLocalEntity(ctx.getSelfId())) {
- log.info("[{}] Going to cleanup rule node states", ctx.getSelfId());
+ log.debug("[{}] Going to cleanup rule node states", ctx.getSelfId());
ctx.clearRuleNodeStates();
}
}
diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
index 8b4dbad1d8..11b35b2a24 100644
--- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
+++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
@@ -1 +1 @@
-System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/material/form-field","@angular/material/checkbox","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/input","@angular/common","@angular/platform-browser","@angular/material/select","@angular/material/core","@angular/material/expansion","@shared/components/button/toggle-password.component","@shared/components/file-input.component","@shared/components/queue/queue-type-list.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/cdk/keycodes","@angular/material/chips","@angular/material/icon","@angular/flex-layout/extended","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/material/tooltip","rxjs/operators","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@home/components/public-api","@shared/components/relation/relation-type-autocomplete.component","@shared/components/entity/entity-subtype-list.component","@home/components/relation/relation-filters.component","rxjs","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component"],(function(e){"use strict";var t,o,r,a,n,l,i,s,m,u,p,d,f,c,g,x,y,b,h,C,F,L,v,I,N,T,q,k,M,S,A,G,D,V,E,P,R,w,O,H,U,K,B,j,z,_,Q,$,W,Y,J,Z,X,ee,te,oe,re,ae,ne,le,ie,se,me,ue,pe,de,fe,ce,ge,xe,ye,be,he,Ce,Fe,Le,ve,Ie;return{setters:[function(e){t=e,o=e.Component,r=e.Pipe,a=e.ViewChild,n=e.forwardRef,l=e.Input,i=e.NgModule},function(e){s=e.RuleNodeConfigurationComponent,m=e.AttributeScope,u=e.telemetryTypeTranslations,p=e.ServiceType,d=e.AlarmSeverity,f=e.alarmSeverityTranslations,c=e.EntitySearchDirection,g=e.entitySearchDirectionTranslations,x=e.EntityType,y=e.PageComponent,b=e.MessageType,h=e.messageTypeNames,C=e,F=e.SharedModule,L=e.AggregationType,v=e.aggregationTranslations,I=e.alarmStatusTranslations,N=e.AlarmStatus},function(e){T=e},function(e){q=e,k=e.Validators,M=e.NgControl,S=e.NG_VALUE_ACCESSOR,A=e.NG_VALIDATORS,G=e.FormControl},function(e){D=e},function(e){V=e},function(e){E=e},function(e){P=e},function(e){R=e},function(e){w=e,O=e.CommonModule},function(e){H=e},function(e){U=e},function(e){K=e},function(e){B=e},function(e){j=e},function(e){z=e},function(e){_=e},function(e){Q=e,$=e.isDefinedAndNotNull,W=e.isNotEmptyStr},function(e){Y=e},function(e){J=e},function(e){Z=e.ENTER,X=e.COMMA,ee=e.SEMICOLON},function(e){te=e},function(e){oe=e},function(e){re=e},function(e){ae=e},function(e){ne=e},function(e){le=e.coerceBooleanProperty},function(e){ie=e},function(e){se=e},function(e){me=e.distinctUntilChanged,ue=e.startWith,pe=e.map,de=e.mergeMap,fe=e.share},function(e){ce=e},function(e){ge=e},function(e){xe=e.HomeComponentsModule},function(e){ye=e},function(e){be=e},function(e){he=e},function(e){Ce=e.of},function(e){Fe=e},function(e){Le=e},function(e){ve=e},function(e){Ie=e}],execute:function(){class Ne extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",Ne),Ne.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.15",ngImport:t,type:Ne,deps:[{token:T.Store},{token:q.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ne.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.15",type:Ne,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"