Browse Source

Merge branch 'develop/3.4' into feature/timeseries-widget-latest-values

pull/6335/head
Igor Kulikov 5 years ago
parent
commit
1b95f32b19
  1. 2
      application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
  2. 29
      application/src/main/data/upgrade/3.3.3/schema_update.sql
  3. 38
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  4. 2
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  5. 15
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  6. 12
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  7. 11
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  8. 2
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  9. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  10. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  11. 86
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
  12. 43
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  14. 83
      application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
  15. 45
      application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
  16. 2
      application/src/main/resources/thingsboard.yml
  17. 31
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  18. 230
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  19. 80
      application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
  20. 23
      application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java
  21. 67
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  22. 4
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  23. 19
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  24. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
  25. 4
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/OtherConfiguration.java
  26. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/TelemetryMappingConfiguration.java
  27. 11
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  28. 2
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
  29. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
  30. 31
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapClientInstanceIds.java
  31. 144
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
  32. 26
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapTaskProvider.java
  33. 6
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  34. 14
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java
  35. 69
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  36. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java
  37. 1
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java
  38. 14
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  39. 16
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  40. 14
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
  41. 11
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java
  42. 4
      common/util/pom.xml
  43. 89
      common/util/src/main/java/org/thingsboard/common/util/KvUtil.java
  44. 37
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  45. 85
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  46. 21
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  47. 30
      dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManager.java
  48. 64
      dao/src/main/java/org/thingsboard/server/dao/cache/EntitiesCacheManagerImpl.java
  49. 24
      dao/src/main/java/org/thingsboard/server/dao/component/BaseComponentDescriptorService.java
  50. 66
      dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java
  51. 39
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  52. 55
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java
  53. 490
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java
  54. 147
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
  55. 17
      dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java
  56. 93
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  57. 67
      dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
  58. 19
      dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
  59. 44
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java
  60. 180
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  61. 60
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  62. 107
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  63. 61
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AdminSettingsDataValidator.java
  64. 57
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java
  65. 50
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java
  66. 108
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java
  67. 39
      dao/src/main/java/org/thingsboard/server/dao/service/validator/AuditLogDataValidator.java
  68. 123
      dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java
  69. 51
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ClientRegistrationTemplateDataValidator.java
  70. 43
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ComponentDescriptorDataValidator.java
  71. 92
      dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java
  72. 68
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java
  73. 75
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceCredentialsDataValidator.java
  74. 147
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java
  75. 524
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java
  76. 90
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java
  77. 36
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeEventDataValidator.java
  78. 88
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java
  79. 40
      dao/src/main/java/org/thingsboard/server/dao/service/validator/EventDataValidator.java
  80. 105
      dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageDataValidator.java
  81. 40
      dao/src/main/java/org/thingsboard/server/dao/service/validator/OtaPackageInfoDataValidator.java
  82. 82
      dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java
  83. 88
      dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java
  84. 68
      dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantDataValidator.java
  85. 69
      dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java
  86. 68
      dao/src/main/java/org/thingsboard/server/dao/service/validator/UserCredentialsDataValidator.java
  87. 136
      dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java
  88. 98
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
  89. 81
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java
  90. 38
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
  91. 50
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java
  92. 39
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  93. 31
      dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java
  94. 141
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  95. 71
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  96. 59
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java
  97. 6
      dao/src/main/resources/sql/schema-entities.sql
  98. 22
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  99. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
  100. 12
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java

2
application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json

File diff suppressed because one or more lines are too long

29
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;
$$;

38
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.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate; import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator; 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.RuleChainManagerActor;
import org.thingsboard.server.actors.ruleChain.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -65,7 +62,7 @@ import java.util.Optional;
@Slf4j @Slf4j
public class TenantActor extends RuleChainManagerActor { public class TenantActor extends RuleChainManagerActor {
private boolean isRuleEngineForCurrentTenant; private boolean isRuleEngine;
private boolean isCore; private boolean isCore;
private ApiUsageState apiUsageState; private ApiUsageState apiUsageState;
@ -78,39 +75,37 @@ public class TenantActor extends RuleChainManagerActor {
@Override @Override
public void init(TbActorCtx ctx) throws TbActorException { public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx); super.init(ctx);
log.info("[{}] Starting tenant actor.", tenantId); log.debug("[{}] Starting tenant actor.", tenantId);
try { try {
Tenant tenant = systemContext.getTenantService().findTenantById(tenantId); Tenant tenant = systemContext.getTenantService().findTenantById(tenantId);
if (tenant == null) { if (tenant == null) {
cantFindTenant = true; cantFindTenant = true;
log.info("[{}] Started tenant actor for missing tenant.", tenantId); log.info("[{}] Started tenant actor for missing tenant.", tenantId);
} else { } else {
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenant.getId()));
// This Service may be started for specific tenant only. // This Service may be started for specific tenant only.
Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant(); Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId());
isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
if (isRuleEngineForCurrentTenant) { if (isRuleEngine) {
try { try {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) { if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {
if (apiUsageState.isReExecEnabled()) { if (getApiUsageState().isReExecEnabled()) {
log.info("[{}] Going to init rule chains", tenantId); log.debug("[{}] Going to init rule chains", tenantId);
initRuleChains(); initRuleChains();
} else { } else {
log.info("[{}] Skip init of the rule chains due to API limits", tenantId); log.info("[{}] Skip init of the rule chains due to API limits", tenantId);
} }
} else { } else {
isRuleEngineForCurrentTenant = false; isRuleEngine = false;
} }
} catch (Exception e) { } catch (Exception e) {
cantFindTenant = true; cantFindTenant = true;
} }
} }
log.info("[{}] Tenant actor started.", tenantId); log.debug("[{}] Tenant actor started.", tenantId);
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Unknown failure", tenantId, e); log.warn("[{}] Unknown failure", tenantId, e);
@ -193,12 +188,12 @@ public class TenantActor extends RuleChainManagerActor {
} }
private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) { private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
if (!isRuleEngineForCurrentTenant) { if (!isRuleEngine) {
log.warn("RECEIVED INVALID MESSAGE: {}", msg); log.warn("RECEIVED INVALID MESSAGE: {}", msg);
return; return;
} }
TbMsg tbMsg = msg.getMsg(); TbMsg tbMsg = msg.getMsg();
if (apiUsageState.isReExecEnabled()) { if (getApiUsageState().isReExecEnabled()) {
if (tbMsg.getRuleChainId() == null) { if (tbMsg.getRuleChainId() == null) {
if (getRootChainActor() != null) { if (getRootChainActor() != null) {
getRootChainActor().tell(msg); getRootChainActor().tell(msg);
@ -222,7 +217,7 @@ public class TenantActor extends RuleChainManagerActor {
} }
private void onRuleChainMsg(RuleChainAwareMsg msg) { private void onRuleChainMsg(RuleChainAwareMsg msg) {
if (apiUsageState.isReExecEnabled()) { if (getApiUsageState().isReExecEnabled()) {
getOrCreateActor(msg.getRuleChainId()).tell(msg); getOrCreateActor(msg.getRuleChainId()).tell(msg);
} }
} }
@ -241,7 +236,7 @@ public class TenantActor extends RuleChainManagerActor {
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) {
ApiUsageState old = apiUsageState; ApiUsageState old = getApiUsageState();
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId)); apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));
if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) { if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) {
log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId); 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); edgeRpcService.updateEdge(tenantId, edge);
} }
} }
} else if (isRuleEngineForCurrentTenant) { } else if (isRuleEngine) {
TbActorRef target = getEntityActorRef(msg.getEntityId()); TbActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) { if (target != null) {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
@ -289,6 +284,13 @@ public class TenantActor extends RuleChainManagerActor {
systemContext.getEdgeRpcService().onEdgeEvent(tenantId, msg.getEdgeId()); 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 { public static class ActorCreator extends ContextBasedCreator {
private final TenantId tenantId; private final TenantId tenantId;

2
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, accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
edge.getId(), edge); edge.getId(), edge);
Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true)); Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser()); onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser());
return savedEdge; return savedEdge;

15
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()) { if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
return getOrFetchState(tenantId, tenantId).getApiUsageState(); return getOrFetchState(tenantId, tenantId).getApiUsageState();
} else { } else {
updateLock.lock(); state = otherUsageStates.get(tenantId);
try { if (state == null) {
state = otherUsageStates.get(tenantId); state = apiUsageStateService.findTenantApiUsageState(tenantId);
if (state == null) { if (state != null) {
state = apiUsageStateService.findTenantApiUsageState(tenantId); otherUsageStates.put(tenantId, state);
if (state != null) {
otherUsageStates.put(tenantId, state);
}
} }
} finally {
updateLock.unlock();
} }
return state; return state;
} }

12
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.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; 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.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.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 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.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; 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.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; 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 org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -225,11 +228,14 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
deviceProfile.setTransportType(DeviceTransportType.LWM2M); deviceProfile.setTransportType(DeviceTransportType.LWM2M);
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); 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(); DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration(); DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
DeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
deviceProfileData.setConfiguration(configuration); deviceProfileData.setConfiguration(configuration);
deviceProfileData.setTransportConfiguration(transportConfiguration); deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfileData.setProvisionConfiguration(provisionConfiguration); deviceProfileData.setProvisionConfiguration(provisionConfiguration);

11
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; 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.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; 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.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -93,7 +93,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override @Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException { public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException {
edge.setRootRuleChainId(ruleChainId); 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); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null);
return savedEdge; return savedEdge;
} }
@ -122,7 +122,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override @Override
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
log.trace("Pushing notification to edge {}", edgeNotificationMsg); log.debug("Pushing notification to edge {}", edgeNotificationMsg);
try { try {
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
@ -153,11 +153,12 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg); relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
break; break;
default: 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) { } catch (Exception e) {
callback.onFailure(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 { } finally {
callback.onSuccess(); callback.onSuccess();
} }

2
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java

@ -68,7 +68,7 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
@Override @Override
protected Edge saveEntity(Edge entity, Map<BulkImportColumnType, String> fields) { protected Edge saveEntity(Edge entity, Map<BulkImportColumnType, String> fields) {
return edgeService.saveEdge(entity, true); return edgeService.saveEdge(entity);
} }
@Override @Override

4
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) { } 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); return Futures.allAsList(result);
} }

7
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 com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; 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.HasCustomerId;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; 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.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; 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.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; 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.edge.rpc.constructor.WidgetsBundleMsgConstructor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@Slf4j @Slf4j
@ -129,6 +131,9 @@ public abstract class BaseEdgeProcessor {
@Autowired @Autowired
protected WidgetTypeService widgetTypeService; protected WidgetTypeService widgetTypeService;
@Autowired
protected DataValidator<Device> deviceValidator;
@Autowired @Autowired
protected EntityDataMsgConstructor entityDataMsgConstructor; protected EntityDataMsgConstructor entityDataMsgConstructor;

86
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.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; 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.Customer;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; 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.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; 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.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData; 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.dao.model.ModelConstants;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; 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.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
import java.util.UUID; import java.util.UUID;
@ -79,38 +80,37 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
String deviceName = deviceUpdateMsg.getName(); String deviceName = deviceUpdateMsg.getName();
Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName);
if (device != null) { if (device != null) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); boolean deviceAlreadyExistsForThisEdge = isDeviceAlreadyExistsOnCloudForThisEdge(tenantId, edge, device);
PageData<EdgeId> pageData; if (deviceAlreadyExistsForThisEdge) {
do { log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " +
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink); "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
boolean update = false; updateDevice(tenantId, edge, deviceUpdateMsg);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { } else {
if (pageData.getData().contains(edge.getId())) { log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
update = true; "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
} String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15);
if (pageData.hasNext()) { Device newDevice;
pageLink = pageLink.nextPageLink(); 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);
} }
ObjectNode body = mapper.createObjectNode();
if (update) { body.put("conflictName", deviceName);
log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
"deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
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());
} else { } else {
log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); 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); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null);
} }
break; break;
@ -131,6 +131,23 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> 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<Void> processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { public ListenableFuture<Void> processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) {
log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg); log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg);
DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB()));
@ -194,7 +211,6 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
if (device == null) { if (device == null) {
device = new Device(); device = new Device();
device.setTenantId(tenantId); device.setTenantId(tenantId);
device.setId(deviceId);
device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId()));
created = true; created = true;
} }
@ -214,6 +230,12 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
deviceUpdateMsg.getDeviceProfileIdLSB())); deviceUpdateMsg.getDeviceProfileIdLSB()));
device.setDeviceProfileId(deviceProfileId); 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); Device savedDevice = deviceService.saveDevice(device, false);
tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false); tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false);
if (created) { if (created) {

43
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()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type,
new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); EdgeId edgeId = null;
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) {
PageData<EdgeId> pageData; edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
}
switch (actionType) { switch (actionType) {
case ADDED: // used only for USER entity case ADDED: // used only for USER entity
case UPDATED: case UPDATED:
case CREDENTIALS_UPDATED: case CREDENTIALS_UPDATED:
do { pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
break; break;
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do { do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
@ -147,7 +140,11 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
break; break;
case DELETED: 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; break;
case ASSIGNED_TO_EDGE: case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_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<EdgeId> 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) { private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<RuleChain> pageData; PageData<RuleChain> pageData;

2
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..."); log.info("Updating TTL cleanup procedure for the event table...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", "schema_event_ttl_procedure.sql"); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", "schema_event_ttl_procedure.sql");
loadSql(schemaUpdateFile, conn); loadSql(schemaUpdateFile, conn);
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Updating schema settings..."); log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;"); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;");

83
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.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.async.DeferredResult; 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.EntityType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; 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.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData; import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery; 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.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink; 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.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType; 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.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService; 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.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -59,7 +68,9 @@ import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -93,9 +104,81 @@ public class DefaultEntityQueryService implements EntityQueryService {
@Override @Override
public PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) { public PageData<EntityData> 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); return entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query);
} }
private void resolveDynamicValuesInPredicates(List<KeyFilterPredicate> 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 <T> void resolveDynamicValue(DynamicValue<T> 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<AttributeKvEntry> 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 @Override
public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) { public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) {
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query); EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query);

45
application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java

@ -31,7 +31,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
public CustomerUserPermissions() { public CustomerUserPermissions() {
super(); super();
put(Resource.ALARM, TenantAdminPermissions.tenantEntityPermissionChecker); put(Resource.ALARM, customerAlarmPermissionChecker);
put(Resource.ASSET, customerEntityPermissionChecker); put(Resource.ASSET, customerEntityPermissionChecker);
put(Resource.DEVICE, customerEntityPermissionChecker); put(Resource.DEVICE, customerEntityPermissionChecker);
put(Resource.CUSTOMER, customerPermissionChecker); put(Resource.CUSTOMER, customerPermissionChecker);
@ -44,6 +44,19 @@ public class CustomerUserPermissions extends AbstractPermissions {
put(Resource.RPC, rpcPermissionChecker); 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 = private static final PermissionChecker customerEntityPermissionChecker =
new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS, new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS,
Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES, 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)) { if (!(entity instanceof HasCustomerId)) {
return false; return false;
} }
if (!operation.equals(Operation.CLAIM_DEVICES) && !user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId())) { return operation.equals(Operation.CLAIM_DEVICES) || user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId());
return false;
}
return true;
} }
}; };
@ -78,10 +88,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!super.hasPermission(user, operation, entityId, entity)) { if (!super.hasPermission(user, operation, entityId, entity)) {
return false; return false;
} }
if (!user.getCustomerId().equals(entityId)) { return user.getCustomerId().equals(entityId);
return false;
}
return true;
} }
}; };
@ -98,10 +105,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!user.getTenantId().equals(dashboard.getTenantId())) { if (!user.getTenantId().equals(dashboard.getTenantId())) {
return false; return false;
} }
if (!dashboard.isAssignedToCustomer(user.getCustomerId())) { return dashboard.isAssignedToCustomer(user.getCustomerId());
return false;
}
return true;
} }
}; };
@ -113,10 +117,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) { if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) {
return false; return false;
} }
if (!user.getId().equals(userId)) { return user.getId().equals(userId);
return false;
}
return true;
} }
}; };
@ -132,10 +133,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) { if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return true; return true;
} }
if (!user.getTenantId().equals(entity.getTenantId())) { return user.getTenantId().equals(entity.getTenantId());
return false;
}
return true;
} }
}; };
@ -151,10 +149,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) { if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return true; return true;
} }
if (!user.getTenantId().equals(entity.getTenantId())) { return user.getTenantId().equals(entity.getTenantId());
return false;
}
return true;
} }
}; };
} }

2
application/src/main/resources/thingsboard.yml

@ -165,7 +165,7 @@ ui:
# Help parameters # Help parameters
help: help:
# Base url for UI help assets # 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: database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records

31
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.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.edge.Edge; 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.HasId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -124,6 +125,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected String username; protected String username;
protected TenantId tenantId; protected TenantId tenantId;
protected CustomerId customerId;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter; private HttpMessageConverter mappingJackson2HttpMessageConverter;
@ -192,6 +194,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
customer.setTitle("Customer"); customer.setTitle("Customer");
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
customerId = savedCustomer.getId();
User customerUser = new User(); User customerUser = new User();
customerUser.setAuthority(Authority.CUSTOMER_USER); customerUser.setAuthority(Authority.CUSTOMER_USER);
@ -254,9 +257,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
} }
private Tenant savedDifferentTenant; private Tenant savedDifferentTenant;
private Customer savedDifferentCustomer;
protected void loginDifferentTenant() throws Exception { protected void loginDifferentTenant() throws Exception {
loginSysAdmin(); loginSysAdmin();
if (savedDifferentTenant != null) {
deleteDifferentTenant();
}
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("Different tenant"); tenant.setTitle("Different tenant");
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class); savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
@ -269,10 +277,27 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
createUserAndLogin(differentTenantAdmin, "testPassword"); 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 { protected void deleteDifferentTenant() throws Exception {
loginSysAdmin(); if (savedDifferentTenant != null) {
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) loginSysAdmin();
.andExpect(status().isOk()); doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
}
} }
protected User createUserAndLogin(User user, String password) throws Exception { protected User createUserAndLogin(User user, String password) throws Exception {

230
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;
}
}

80
application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java

@ -16,10 +16,13 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import org.awaitility.Awaitility;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; 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.id.EntityId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter; 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.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink; 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.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -299,6 +306,79 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
List<String> deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList()); List<String> deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures); 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<Double> dynamicValue =
new DynamicValue<>(DynamicValueSourceType.CURRENT_TENANT, "dynamicValue");
FilterPredicateValue<Double> predicateValue = new FilterPredicateValue<>(0.0, null, dynamicValue);
predicate.setValue(predicateValue);
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> 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<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List<EntityKey> 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<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());
return loadedEntities.size() == numOfDevices;
});
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
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);
}
} }
} }

23
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 {
}

67
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.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; 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.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; 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.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; 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.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; 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.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg; 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.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
@ -331,6 +334,34 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertNotNull(jsonNode.get("test")); 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 @Test
public void testDevices() throws Exception { public void testDevices() throws Exception {
// 1 // 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 @Test
public void testAssets() throws Exception { public void testAssets() throws Exception {
// 1 // 1
@ -547,8 +612,6 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
ruleChainMetaData.addConnectionInfo(0, 2, "fail"); ruleChainMetaData.addConnectionInfo(0, 2, "fail");
ruleChainMetaData.addConnectionInfo(1, 2, "success"); ruleChainMetaData.addConnectionInfo(1, 2, "success");
ruleChainMetaData.addRuleChainConnectionInfo(2, edge.getRootRuleChainId(), "success", mapper.createObjectNode());
doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
} }

4
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -88,6 +88,9 @@ public class EdgeImitator {
@Getter @Getter
private List<AbstractMessage> downlinkMsgs; private List<AbstractMessage> downlinkMsgs;
@Getter
private UplinkResponseMsg latestResponseMsg;
private boolean connected = false; private boolean connected = false;
public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException { 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) { private void onUplinkResponse(UplinkResponseMsg msg) {
log.info("onUplinkResponse: {}", msg); log.info("onUplinkResponse: {}", msg);
latestResponseMsg = msg;
responsesLatch.countDown(); responsesLatch.countDown();
} }

19
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.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants; 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.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.PageData; 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.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
@ -240,16 +242,27 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(mapper.valueToTree(configuration1)); 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.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); rootMetaData = saveRuleChainMetaData(rootMetaData);
Assert.assertNotNull(rootMetaData); Assert.assertNotNull(rootMetaData);
rootRuleChain = getRuleChain(rootRuleChain.getId()); rootRuleChain = getRuleChain(rootRuleChain.getId());
Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId()); Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId());
RuleChainMetaData secondaryMetaData = new RuleChainMetaData(); RuleChainMetaData secondaryMetaData = new RuleChainMetaData();
secondaryMetaData.setRuleChainId(secondaryRuleChain.getId()); secondaryMetaData.setRuleChainId(secondaryRuleChain.getId());

2
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java

@ -42,7 +42,7 @@ public interface EdgeService {
Optional<Edge> findEdgeByRoutingKey(TenantId tenantId, String routingKey); Optional<Edge> findEdgeByRoutingKey(TenantId tenantId, String routingKey);
Edge saveEdge(Edge edge, boolean doValidate); Edge saveEdge(Edge edge);
Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId); Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId);

4
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; package org.thingsboard.server.common.data.device.profile.lwm2m;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
@Data @Data
@NoArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class OtherConfiguration extends PowerSavingConfiguration { public class OtherConfiguration extends PowerSavingConfiguration {

5
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; package org.thingsboard.server.common.data.device.profile.lwm2m;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; import lombok.NoArgsConstructor;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@Data @Data
@NoArgsConstructor
@AllArgsConstructor
public class TelemetryMappingConfiguration { public class TelemetryMappingConfiguration {
private Map<String, String> keyName; private Map<String, String> keyName;

11
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java

@ -59,15 +59,4 @@ public class RuleChainMetaData {
} }
connections.add(connectionInfo); 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);
}
} }

2
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 { private String validatePayload(UUID sessionId, Request inbound, boolean isEmptyPayloadAllowed) throws AdaptorException {
String payload = inbound.getPayloadString(); String payload = inbound.getPayloadString();
if (payload == null) { if (payload == null) {
log.warn("[{}] Payload is empty!", sessionId); log.debug("[{}] Payload is empty!", sessionId);
if (!isEmptyPayloadAllowed) { if (!isEmptyPayloadAllowed) {
throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
} }

27
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.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession; import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSession;
import org.eclipse.leshan.server.bootstrap.DefaultBootstrapSessionManager; 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.LwM2mBootstrapModelProvider;
import org.eclipse.leshan.server.model.StandardBootstrapModelProvider; import org.eclipse.leshan.server.model.StandardBootstrapModelProvider;
import org.eclipse.leshan.server.security.BootstrapSecurityStore; 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.common.transport.TransportService;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapConfigStoreTaskProvider; 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.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapTaskProvider;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.ArrayList; import java.util.ArrayList;
@ -48,10 +50,10 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.LO
@Slf4j @Slf4j
public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager { public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSessionManager {
private BootstrapSecurityStore bsSecurityStore; private final BootstrapSecurityStore bsSecurityStore;
private SecurityChecker securityChecker; private final SecurityChecker securityChecker;
private BootstrapTaskProvider tasksProvider; private final LwM2MBootstrapTaskProvider tasksProvider;
private LwM2mBootstrapModelProvider modelProvider; private final LwM2mBootstrapModelProvider modelProvider;
private TransportService transportService; private TransportService transportService;
/** /**
@ -73,7 +75,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
* @param securityChecker used to accept or refuse new {@link BootstrapSession}. * @param securityChecker used to accept or refuse new {@link BootstrapSession}.
*/ */
public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker, public LwM2mDefaultBootstrapSessionManager(BootstrapSecurityStore bsSecurityStore, SecurityChecker securityChecker,
BootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) { LwM2MBootstrapTaskProvider tasksProvider, LwM2mBootstrapModelProvider modelProvider) {
super(bsSecurityStore, securityChecker, tasksProvider, modelProvider); super(bsSecurityStore, securityChecker, tasksProvider, modelProvider);
this.bsSecurityStore = bsSecurityStore; this.bsSecurityStore = bsSecurityStore;
this.securityChecker = securityChecker; this.securityChecker = securityChecker;
@ -100,7 +102,12 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
} }
DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized); DefaultBootstrapSession session = new DefaultBootstrapSession(request, clientIdentity, authorized);
if (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())); String.format("%s: Bootstrap session started...", LOG_LWM2M_INFO, request.getEndpointName()));
} }
return session; return session;
@ -108,7 +115,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
@Override @Override
public boolean hasConfigFor(BootstrapSession session) { public boolean hasConfigFor(BootstrapSession session) {
BootstrapTaskProvider.Tasks firstTasks = tasksProvider.getTasks(session, null); BootstrapTaskProvider.Tasks firstTasks = this.tasksProvider.getTasks(session, null);
if (firstTasks == null) { if (firstTasks == null) {
return false; return false;
} }
@ -147,7 +154,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
return requestsToSend.remove(0); return requestsToSend.remove(0);
} else { } else {
if (session.hasMoreTasks()) { if (session.hasMoreTasks()) {
BootstrapTaskProvider.Tasks nextTasks = tasksProvider.getTasks(session, session.getResponses()); BootstrapTaskProvider.Tasks nextTasks = this.tasksProvider.getTasks(session, session.getResponses());
if (nextTasks == null) { if (nextTasks == null) {
session.setMoreTasks(false); session.setMoreTasks(false);
return new BootstrapFinishRequest(); return new BootstrapFinishRequest();
@ -178,6 +185,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
// on success for bootstrap finish request we stop the session // on success for bootstrap finish request we stop the session
this.sendLogs(bsSession.getEndpoint(), this.sendLogs(bsSession.getEndpoint(),
String.format("%s: receives success response for bootstrap finish.", LOG_LWM2M_INFO)); String.format("%s: receives success response for bootstrap finish.", LOG_LWM2M_INFO));
this.tasksProvider.remove(bsSession.getEndpoint());
return BootstrapPolicy.finished(); return BootstrapPolicy.finished();
} }
} }
@ -199,6 +207,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
// on response error for bootstrap finish request we stop the session // on response error for bootstrap finish request we stop the session
this.sendLogs(bsSession.getEndpoint(), this.sendLogs(bsSession.getEndpoint(),
String.format("%s: error response for request bootstrap finish. Stop the session: %s", LOG_LWM2M_ERROR, bsSession.toString())); 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(); return BootstrapPolicy.failed();
} }
} }
@ -215,12 +224,14 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
@Override @Override
public void end(BootstrapSession bsSession) { public void end(BootstrapSession bsSession) {
this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO)); this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session finished.", LOG_LWM2M_INFO));
this.tasksProvider.remove(bsSession.getEndpoint());
} }
@Override @Override
public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) { public void failed(BootstrapSession bsSession, BootstrapFailureCause cause) {
this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session failed because of %s", LOG_LWM2M_ERROR, this.sendLogs(bsSession.getEndpoint(), String.format("%s: Bootstrap session failed because of %s", LOG_LWM2M_ERROR,
cause.toString())); cause.toString()));
this.tasksProvider.remove(bsSession.getEndpoint());
} }
private void sendLogs(String endpointName, String logMsg) { private void sendLogs(String endpointName, String logMsg) {

31
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<serverId (shortId), InstanceId>
*/
private Map<Integer, Integer> securityInstances = new HashMap<>();
private Map<Integer, Integer> serverInstances = new HashMap<>();
}

144
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.BootstrapConfig;
import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.BootstrapConfigStore;
import org.eclipse.leshan.server.bootstrap.BootstrapSession; 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.BootstrapUtil;
import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
@ -42,28 +42,36 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap; 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 java.util.stream.Collectors;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest; import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.BOOTSTRAP_DEFAULT_SHORT_ID;
@Slf4j @Slf4j
public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvider { public class LwM2MBootstrapConfigStoreTaskProvider implements LwM2MBootstrapTaskProvider {
protected final ReadWriteLock readWriteLock;
protected final Lock writeLock;
private BootstrapConfigStore store; private BootstrapConfigStore store;
private Map<Integer, String> supportedObjects; private Map<Integer, String> supportedObjects;
/** /**
* Map<serverId, InstanceId> * Map<sEndpoint, LwM2MBootstrapClientInstanceIds: securityInstances, serverInstances>
*/ */
protected Map<Integer, Integer> securityInstances; protected Map<String, LwM2MBootstrapClientInstanceIds> lwM2MBootstrapSessionClients;
protected Map<Integer, Integer> serverInstances;
protected Integer bootstrapServerIdOld;
protected Integer bootstrapServerIdNew;
public LwM2MBootstrapConfigStoreTaskProvider(BootstrapConfigStore store) { public LwM2MBootstrapConfigStoreTaskProvider(BootstrapConfigStore store) {
this.store = store; this.store = store;
this.lwM2MBootstrapSessionClients = new ConcurrentHashMap<>();
readWriteLock = new ReentrantReadWriteLock();
writeLock = readWriteLock.writeLock();
} }
@Override @Override
@ -91,13 +99,13 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0); BootstrapDiscoverResponse discoverResponse = (BootstrapDiscoverResponse) previousResponse.get(0);
if (discoverResponse.isSuccess()) { if (discoverResponse.isSuccess()) {
this.initAfterBootstrapDiscover(discoverResponse); this.initAfterBootstrapDiscover(discoverResponse);
findSecurityInstanceId(discoverResponse.getObjectLinks()); findSecurityInstanceId(discoverResponse.getObjectLinks(), session.getEndpoint());
} else { } else {
log.warn( log.warn(
"Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}", "Bootstrap Discover return error {} : to continue bootstrap session without autoIdForSecurityObject mode. {}",
discoverResponse, session); discoverResponse, session);
} }
if (this.securityInstances.get(0) == null) { if (this.lwM2MBootstrapSessionClients.get(session.getEndpoint()).getSecurityInstances().get(BOOTSTRAP_DEFAULT_SHORT_ID) == null) {
log.error( log.error(
"Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}", "Unable to find bootstrap server instance in Security Object (0) in response {}: unable to continue bootstrap session with autoIdForSecurityObject mode. {}",
discoverResponse, session); discoverResponse, session);
@ -109,8 +117,12 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
return tasks; return tasks;
} }
BootstrapReadResponse readResponse = (BootstrapReadResponse) previousResponse.get(0); BootstrapReadResponse readResponse = (BootstrapReadResponse) previousResponse.get(0);
Integer bootstrapServerIdOld = null;
if (readResponse.isSuccess()) { 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 { } else {
log.warn( log.warn(
"Bootstrap ReadResponse return error {} : to continue bootstrap session without find Server Instance Id. {}", "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 // create requests from config
tasks.requestsToSend = this.toRequests(config, tasks.requestsToSend = this.toRequests(config,
config.contentFormat != null ? config.contentFormat : session.getContentFormat()); config.contentFormat != null ? config.contentFormat : session.getContentFormat(),
bootstrapServerIdOld, session.getEndpoint());
} else { } else {
// create requests from config // create requests from config
tasks.requestsToSend = BootstrapUtil.toRequests(config, tasks.requestsToSend = BootstrapUtil.toRequests(config,
@ -132,9 +145,15 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
return config.autoIdForSecurityObject; 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); log.info("Object after discover: [{}]", objectLinks);
this.securityInstances = new HashMap<>();
for (Link link : objectLinks) { for (Link link : objectLinks) {
if (link.getUriReference().startsWith("/0/")) { if (link.getUriReference().startsWith("/0/")) {
try { try {
@ -142,15 +161,15 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
if (path.isObjectInstance()) { if (path.isObjectInstance()) {
if (link.getLinkParams().containsKey("ssid")) { if (link.getLinkParams().containsKey("ssid")) {
int serverId = Integer.parseInt(link.getLinkParams().get("ssid").getUnquoted()); int serverId = Integer.parseInt(link.getLinkParams().get("ssid").getUnquoted());
if (!this.securityInstances.containsKey(serverId)) { if (!lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(serverId)) {
this.securityInstances.put(serverId, path.getObjectInstanceId()); lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(serverId, path.getObjectInstanceId());
} else { } else {
log.error("Invalid lwm2mSecurityInstance by [{}]", path.getObjectInstanceId()); 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 { } else {
if (!this.securityInstances.containsKey(0)) { if (!this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(0)) {
this.securityInstances.put(0, path.getObjectInstanceId()); this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().put(BOOTSTRAP_DEFAULT_SHORT_ID, path.getObjectInstanceId());
} else { } else {
log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId()); log.error("Invalid bootstrapSecurityInstance by [{}]", path.getObjectInstanceId());
} }
@ -164,8 +183,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
} }
} }
protected void findServerInstanceId(BootstrapReadResponse readResponse) { protected void findServerInstanceId(BootstrapReadResponse readResponse, String endpoint) {
this.serverInstances = new HashMap<>();
try { try {
((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> { ((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(); 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 { } else {
shortId = (int) shId; shortId = (int) shId;
} }
serverInstances.put(shortId, instance.getId()); this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().put(shortId, instance.getId());
}); });
} catch (Exception e) { } catch (Exception e) {
log.error("Failed find Server Instance Id. ", 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() { protected Integer findBootstrapServerId(String endpoint) {
Map<Integer, Integer> filteredMap = this.serverInstances.entrySet() Integer bootstrapServerIdOld = null;
.stream().filter(x -> !this.securityInstances.containsKey(x.getKey())) Map<Integer, Integer> 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)); .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if (filteredMap.size() > 0) { if (filteredMap.size() > 0) {
this.bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get(); bootstrapServerIdOld = filteredMap.keySet().stream().findFirst().get();
} }
return bootstrapServerIdOld;
} }
public BootstrapConfigStore getStore() { public BootstrapConfigStore getStore() {
@ -213,46 +230,51 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
public List<BootstrapDownlinkRequest<? extends LwM2mResponse>> toRequests(BootstrapConfig bootstrapConfig, public List<BootstrapDownlinkRequest<? extends LwM2mResponse>> toRequests(BootstrapConfig bootstrapConfig,
ContentFormat contentFormat) { ContentFormat contentFormat,
Integer bootstrapServerIdOld,
String endpoint) {
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests = new ArrayList<>(); List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requests = new ArrayList<>();
Set<String> pathsDelete = new HashSet<>(); Set<String> pathsDelete = new HashSet<>();
List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsWrite = new ArrayList<>(); List<BootstrapDownlinkRequest<? extends LwM2mResponse>> requestsWrite = new ArrayList<>();
boolean isBsServer = false; boolean isBsServer = false;
boolean isLwServer = false; boolean isLwServer = false;
/** Map<serverId, InstanceId> */ /** Map<serverId ("Short Server ID"), InstanceId> */
Map<Integer, Integer> instances = new HashMap<>(); Map<Integer, Integer> instances = new HashMap<>();
Integer bootstrapServerIdNew = null;
// handle security // 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()) { for (BootstrapConfig.ServerSecurity security : new TreeMap<>(bootstrapConfig.security).values()) {
if (security.bootstrapServer) { if (security.bootstrapServer) {
requestsWrite.add(toWriteRequest(this.securityInstances.get(0), security, contentFormat)); requestsWrite.add(toWriteRequest(bootstrapSecurityInstanceId, security, contentFormat));
isBsServer = true; isBsServer = true;
this.bootstrapServerIdNew = security.serverId; bootstrapServerIdNew = security.serverId;
instances.put(security.serverId, this.securityInstances.get(0)); instances.put(security.serverId, bootstrapSecurityInstanceId);
} else { } else {
if (id == this.securityInstances.get(0)) { if (lwm2mSecurityInstanceId == bootstrapSecurityInstanceId) {
id++; lwm2mSecurityInstanceId++;
} }
requestsWrite.add(toWriteRequest(id, security, contentFormat)); requestsWrite.add(toWriteRequest(lwm2mSecurityInstanceId, security, contentFormat));
instances.put(security.serverId, id); instances.put(security.serverId, lwm2mSecurityInstanceId);
isLwServer = true; isLwServer = true;
if (!isBsServer && this.securityInstances.containsKey(security.serverId) && id != this.securityInstances.get(security.serverId)) { if (!isBsServer && this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().containsKey(security.serverId) &&
pathsDelete.add("/0/" + this.securityInstances.get(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 * If there is an instance in the serverInstances with serverId which we replace in the securityInstances
*/ */
// find serverId in securityInstances by id (instance) // find serverId in securityInstances by id (instance)
Integer serverIdOld = null; Integer serverIdOld = null;
for (Map.Entry<Integer, Integer> entry : this.securityInstances.entrySet()) { for (Map.Entry<Integer, Integer> entry : this.lwM2MBootstrapSessionClients.get(endpoint).getSecurityInstances().entrySet()) {
if (entry.getValue().equals(id)) { if (entry.getValue().equals(lwm2mSecurityInstanceId)) {
serverIdOld = entry.getKey(); serverIdOld = entry.getKey();
} }
} }
if (!isBsServer && serverIdOld != null && this.serverInstances.containsKey(serverIdOld)) { if (!isBsServer && serverIdOld != null && this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(serverIdOld)) {
pathsDelete.add("/1/" + this.serverInstances.get(serverIdOld)); pathsDelete.add("/1/" + this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(serverIdOld));
} }
id++; lwm2mSecurityInstanceId++;
} }
} }
// handle server // handle server
@ -261,12 +283,13 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
requestsWrite.add(toWriteRequest(securityInstanceId, server.getValue(), contentFormat)); requestsWrite.add(toWriteRequest(securityInstanceId, server.getValue(), contentFormat));
if (!isBsServer) { if (!isBsServer) {
/** Delete instance if bootstrapServerIdNew not equals bootstrapServerIdOld or securityInstanceBsIdNew not equals serverInstanceBsIdOld */ /** Delete instance if bootstrapServerIdNew not equals bootstrapServerIdOld or securityInstanceBsIdNew not equals serverInstanceBsIdOld */
if (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew && if (bootstrapServerIdNew != null && server.getValue().shortId == bootstrapServerIdNew &&
(this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) { (bootstrapServerIdNew != bootstrapServerIdOld || securityInstanceId != this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().get(bootstrapServerIdOld))) {
pathsDelete.add("/1/" + this.serverInstances.get(this.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 */ /** 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)) { } else if (this.lwM2MBootstrapSessionClients.get(endpoint).getServerInstances().containsKey(server.getValue().shortId) &&
pathsDelete.add("/1/" + this.serverInstances.get(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); return (requests);
} }
private void initSupportedObjectsDefault() { private void initSupportedObjectsDefault() {
this.supportedObjects = new HashMap<>(); this.supportedObjects = new HashMap<>();
this.supportedObjects.put(0, "1.1"); this.supportedObjects.put(0, "1.1");
this.supportedObjects.put(1, "1.1"); this.supportedObjects.put(1, "1.1");
this.supportedObjects.put(2, "1.0"); 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();
}
}
} }

26
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);
}

6
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.DtlsConfig;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; 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.DefaultLwM2mDecoder;
import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; 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.LeshanServer;
import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.LeshanServerBuilder;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; 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.springframework.stereotype.Component;
import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -40,6 +44,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.security.cert.X509Certificate; 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_CIPHER_SUITES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_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.getRegistrationService().addListener(lhServerCertListener.registrationListener);
this.server.getPresenceService().addListener(lhServerCertListener.presenceListener); this.server.getPresenceService().addListener(lhServerCertListener.presenceListener);
this.server.getObservationService().addListener(lhServerCertListener.observationListener); this.server.getObservationService().addListener(lhServerCertListener.observationListener);
this.server.getSendService().addListener(lhServerCertListener.sendListener);
log.info("Started LwM2M transport server."); log.info("Started LwM2M transport server.");
} }

14
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; package org.thingsboard.server.transport.lwm2m.server;
import lombok.extern.slf4j.Slf4j; 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.CompositeObservation;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.observation.SingleObservation; 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.ObserveCompositeResponse;
import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.server.observation.ObservationListener; 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.Registration;
import org.eclipse.leshan.server.registration.RegistrationListener; import org.eclipse.leshan.server.registration.RegistrationListener;
import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.RegistrationUpdate;
import org.eclipse.leshan.server.send.SendListener;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.convertObjectIdToVersionedId; 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()); log.trace("Successful start newObservation {}.", ((SingleObservation)observation).getPath());
} }
}; };
public final SendListener sendListener = new SendListener() {
@Override
public void dataReceived(Registration registration, Map<String, LwM2mNode> map, SendRequest sendRequest) {
if (registration != null) {
service.onUpdateValueWithSendRequest(registration, sendRequest);
}
}
};
} }

69
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.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.Observation;
import org.eclipse.leshan.core.request.CreateRequest; import org.eclipse.leshan.core.request.*;
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.WriteRequest.Mode; import org.eclipse.leshan.core.request.WriteRequest.Mode;
import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ObserveResponse;
import org.eclipse.leshan.core.response.ReadCompositeResponse; 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.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.*;
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.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -347,12 +334,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE, responseCode); this.updateResourcesValue(lwM2MClient, lwM2mResource, path, Mode.UPDATE, responseCode);
} }
} }
if (clientContext.awake(lwM2MClient)) { tryAwake(lwM2MClient);
// clientContext.awake calls clientContext.update
log.debug("[{}] Device is awake", lwM2MClient.getEndpoint());
} else {
clientContext.update(lwM2MClient);
}
} }
} }
@ -373,12 +355,37 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
} }
}); });
clientContext.update(lwM2MClient); clientContext.update(lwM2MClient);
if (clientContext.awake(lwM2MClient)) { tryAwake(lwM2MClient);
// clientContext.awake calls clientContext.update }
log.debug("[{}] Device is awake", lwM2MClient.getEndpoint()); }
} else {
clientContext.update(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(); 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);
}
}
} }

3
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.observation.Observation;
import org.eclipse.leshan.core.request.CreateRequest; 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.WriteCompositeRequest;
import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse;
@ -46,6 +47,8 @@ public interface LwM2mUplinkMsgHandler {
void onUpdateValueAfterReadCompositeResponse(Registration registration, ReadCompositeResponse response); void onUpdateValueAfterReadCompositeResponse(Registration registration, ReadCompositeResponse response);
void onUpdateValueWithSendRequest(Registration registration, SendRequest sendRequest);
void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile); void onDeviceProfileUpdate(TransportProtos.SessionInfoProto sessionInfo, DeviceProfile deviceProfile);
void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt); void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional<DeviceProfile> deviceProfileOpt);

1
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_INFO = "info";
public static final String LOG_LWM2M_ERROR = "error"; public static final String LOG_LWM2M_ERROR = "error";
public static final String LOG_LWM2M_WARN = "warn"; public static final String LOG_LWM2M_WARN = "warn";
public static final int BOOTSTRAP_DEFAULT_SHORT_ID = 0;
public enum LwM2MClientStrategy { public enum LwM2MClientStrategy {
CLIENT_STRATEGY_1(1, "Read only resources marked as observation"), CLIENT_STRATEGY_1(1, "Read only resources marked as observation"),

14
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); log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName);
ctx.close(); ctx.close();
} }
} catch (RuntimeException | AdaptorException e) { } catch (RuntimeException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close(); ctx.close();
} catch (AdaptorException e) {
log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close();
} }
break; break;
case PINGREQ: case PINGREQ:
@ -354,9 +357,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
default: default:
ack(ctx, msgId); ack(ctx, msgId);
} }
} catch (RuntimeException | AdaptorException e) { } catch (RuntimeException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close(); 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); ack(ctx, msgId);
} }
} catch (AdaptorException e) { } 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); log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId);
ctx.close(); ctx.close();
} }
@ -749,7 +755,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
} catch (Exception e) { } 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) { if (!activityReported) {

16
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java

@ -61,7 +61,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
} catch (IllegalStateException | JsonSyntaxException ex) { } 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); throw new AdaptorException(ex);
} }
} }
@ -72,7 +72,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
} catch (IllegalStateException | JsonSyntaxException ex) { } 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); throw new AdaptorException(ex);
} }
} }
@ -83,7 +83,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload); return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload);
} catch (IllegalStateException | JsonSyntaxException ex) { } 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); throw new AdaptorException(ex);
} }
} }
@ -164,7 +164,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return new JsonParser().parse(payload); return new JsonParser().parse(payload);
} catch (JsonSyntaxException ex) { } catch (JsonSyntaxException ex) {
log.warn("Payload is in incorrect format: {}", payload); log.debug("Payload is in incorrect format: {}", payload);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@ -186,7 +186,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
} }
return result.build(); return result.build();
} catch (RuntimeException e) { } 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); throw new AdaptorException(e);
} }
} }
@ -198,7 +198,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
String payload = inbound.payload().toString(UTF8); String payload = inbound.payload().toString(UTF8);
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build();
} catch (RuntimeException e) { } catch (RuntimeException e) {
log.warn("Failed to decode rpc response", e); log.debug("Failed to decode rpc response", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -210,7 +210,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase); int requestId = getRequestId(topicName, topicBase);
return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId); return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
} catch (IllegalStateException | JsonSyntaxException ex) { } 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); 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 { private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException {
String payload = payloadData.toString(UTF8); String payload = payloadData.toString(UTF8);
if (payload == null) { if (payload == null) {
log.warn("[{}] Payload is empty!", sessionId); log.debug("[{}] Payload is empty!", sessionId);
if (!isEmptyPayloadAllowed) { if (!isEmptyPayloadAllowed) {
throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
} }

14
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java

@ -52,7 +52,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor))); return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
} catch (Exception e) { } 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); throw new AdaptorException(e);
} }
} }
@ -65,7 +65,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor))); return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor)));
} catch (Exception e) { } 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); throw new AdaptorException(e);
} }
} }
@ -76,7 +76,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes); return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes);
} catch (InvalidProtocolBufferException e) { } 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); throw new AdaptorException(e);
} }
} }
@ -89,7 +89,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase); int requestId = getRequestId(topicName, topicBase);
return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId); return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId);
} catch (InvalidProtocolBufferException e) { } 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); throw new AdaptorException(e);
} }
} }
@ -105,7 +105,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor)); JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor));
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build(); return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build();
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to decode rpc response", e); log.debug("Failed to decode rpc response", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -118,7 +118,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase); int requestId = getRequestId(topicName, topicBase);
return ProtoConverter.convertToServerRpcRequest(bytes, requestId); return ProtoConverter.convertToServerRpcRequest(bytes, requestId);
} catch (InvalidProtocolBufferException e) { } 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); throw new AdaptorException(e);
} }
} }
@ -129,7 +129,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return ProtoConverter.convertToProvisionRequestMsg(bytes); return ProtoConverter.convertToProvisionRequestMsg(bytes);
} catch (InvalidProtocolBufferException ex) { } catch (InvalidProtocolBufferException ex) {
log.warn("Failed to decode provision request", ex); log.debug("Failed to decode provision request", ex);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }

11
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; package org.thingsboard.server.transport.mqtt.limits;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.haproxy.HAProxyMessage; import io.netty.handler.codec.haproxy.HAProxyMessage;
@ -60,7 +61,15 @@ public class ProxyIpFilter extends ChannelInboundHandlerAdapter {
private void closeChannel(ChannelHandlerContext ctx) { private void closeChannel(ChannelHandlerContext ctx) {
while (ctx.pipeline().last() != this) { 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.pipeline().remove(this);
ctx.close(); ctx.close();

4
common/util/pom.xml

@ -84,6 +84,10 @@
<artifactId>awaitility</artifactId> <artifactId>awaitility</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>data</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

89
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;
}
}
}

37
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory; 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.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; 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.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService; 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.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -59,7 +55,6 @@ import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -82,10 +77,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private AlarmDao alarmDao; private AlarmDao alarmDao;
@Autowired @Autowired
private TenantDao tenantDao; private EntityService entityService;
@Autowired @Autowired
private EntityService entityService; private DataValidator<Alarm> alarmDataValidator;
protected ExecutorService readResultsProcessingExecutor; protected ExecutorService readResultsProcessingExecutor;
@ -412,32 +407,4 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()); ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId());
return Futures.transform(entity, function, readResultsProcessingExecutor); return Futures.transform(entity, function, readResultsProcessingExecutor);
} }
private DataValidator<Alarm> 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!");
}
}
}
};
} }

85
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 lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; 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.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; 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.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; 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.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery; 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.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
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.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; 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.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; 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.common.data.CacheConstants.ASSET_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs; 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.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@ -86,17 +75,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
private AssetDao assetDao; private AssetDao assetDao;
@Autowired @Autowired
private TenantDao tenantDao; private EntitiesCacheManager cacheManager;
@Autowired @Autowired
private CustomerDao customerDao; private DataValidator<Asset> assetValidator;
@Autowired
private CacheManager cacheManager;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) { 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); 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()); 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 @Override
public PageData<Asset> findAssetsByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<Asset> findAssetsByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, 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); return assetDao.findAssetsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
} }
private DataValidator<Asset> assetValidator =
new DataValidator<Asset>() {
@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<TenantId, Asset> tenantAssetsRemover = private PaginatedRemover<TenantId, Asset> tenantAssetsRemover =
new PaginatedRemover<TenantId, Asset>() { new PaginatedRemover<TenantId, Asset>() {

21
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.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
import org.thingsboard.server.common.data.audit.ActionStatus; 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.audit.sink.AuditLogSink;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.entity.EntityService; 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.service.DataValidator;
import org.thingsboard.common.util.JacksonUtil;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
@ -80,6 +79,9 @@ public class AuditLogServiceImpl implements AuditLogService {
@Autowired @Autowired
private AuditLogSink auditLogSink; private AuditLogSink auditLogSink;
@Autowired
private DataValidator<AuditLog> auditLogValidator;
@Override @Override
public PageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List<ActionType> actionTypes, TimePageLink pageLink) { public PageData<AuditLog> findAuditLogsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, List<ActionType> actionTypes, TimePageLink pageLink) {
log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink); log.trace("Executing findAuditLogsByTenantIdAndCustomerId [{}], [{}], [{}]", tenantId, customerId, pageLink);
@ -389,19 +391,4 @@ public class AuditLogServiceImpl implements AuditLogService {
return Futures.allAsList(futures); return Futures.allAsList(futures);
} }
private DataValidator<AuditLog> auditLogValidator =
new DataValidator<AuditLog>() {
@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!");
}
}
};
} }

30
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);
}

64
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));
}
}

24
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.JsonSchemaFactory;
import com.github.fge.jsonschema.main.JsonValidator; import com.github.fge.jsonschema.main.JsonValidator;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.ComponentDescriptorId; 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.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -49,6 +47,9 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic
@Autowired @Autowired
private ComponentDescriptorDao componentDescriptorDao; private ComponentDescriptorDao componentDescriptorDao;
@Autowired
private DataValidator<ComponentDescriptor> componentValidator;
@Override @Override
public ComponentDescriptor saveComponent(TenantId tenantId, ComponentDescriptor component) { public ComponentDescriptor saveComponent(TenantId tenantId, ComponentDescriptor component) {
componentValidator.validate(component, data -> TenantId.SYS_TENANT_ID); componentValidator.validate(component, data -> TenantId.SYS_TENANT_ID);
@ -100,23 +101,4 @@ public class BaseComponentDescriptorService implements ComponentDescriptorServic
throw new IncorrectParameterException(e.getMessage(), e); throw new IncorrectParameterException(e.getMessage(), e);
} }
} }
private DataValidator<ComponentDescriptor> componentValidator =
new DataValidator<ComponentDescriptor>() {
@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!");
}
}
};
} }

66
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.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer; 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.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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.asset.AssetService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; 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.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; 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.usagerecord.ApiUsageStateService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
@ -55,7 +47,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j @Slf4j
public class CustomerServiceImpl extends AbstractEntityService implements CustomerService { 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_CUSTOMER_ID = "Incorrect customerId ";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@ -65,9 +57,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
@Autowired @Autowired
private UserService userService; private UserService userService;
@Autowired
private TenantDao tenantDao;
@Autowired @Autowired
private AssetService assetService; private AssetService assetService;
@ -84,8 +73,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
private ApiUsageStateService apiUsageStateService; private ApiUsageStateService apiUsageStateService;
@Autowired @Autowired
@Lazy private DataValidator<Customer> customerValidator;
private TbTenantProfileCache tenantProfileCache;
@Override @Override
public Customer findCustomerById(TenantId tenantId, CustomerId customerId) { public Customer findCustomerById(TenantId tenantId, CustomerId customerId) {
@ -171,56 +159,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom
customersByTenantRemover.removeEntities(tenantId, tenantId); customersByTenantRemover.removeEntities(tenantId, tenantId);
} }
private DataValidator<Customer> customerValidator =
new DataValidator<Customer>() {
@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<TenantId, Customer> customersByTenantRemover = private PaginatedRemover<TenantId, Customer> customersByTenantRemover =
new PaginatedRemover<TenantId, Customer>() { new PaginatedRemover<TenantId, Customer>() {

39
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 com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo; 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.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; 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.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; 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.customer.CustomerDao;
import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.edge.EdgeDao;
import org.thingsboard.server.dao.entity.AbstractEntityService; 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.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; 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; import static org.thingsboard.server.dao.service.Validator.validateId;
@ -61,9 +54,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
@Autowired @Autowired
private DashboardInfoDao dashboardInfoDao; private DashboardInfoDao dashboardInfoDao;
@Autowired
private TenantDao tenantDao;
@Autowired @Autowired
private CustomerDao customerDao; private CustomerDao customerDao;
@ -71,8 +61,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
private EdgeDao edgeDao; private EdgeDao edgeDao;
@Autowired @Autowired
@Lazy private DataValidator<Dashboard> dashboardValidator;
private TbTenantProfileCache tenantProfileCache;
@Override @Override
public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) { public Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId) {
@ -290,32 +279,6 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name); return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name);
} }
private DataValidator<Dashboard> dashboardValidator =
new DataValidator<Dashboard>() {
@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<TenantId, DashboardInfo> tenantDashboardsRemover = private PaginatedRemover<TenantId, DashboardInfo> tenantDashboardsRemover =
new PaginatedRemover<TenantId, DashboardInfo>() { new PaginatedRemover<TenantId, DashboardInfo>() {

55
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.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; 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.StringUtils;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; 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.LwM2MBootstrapClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; 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.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.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.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.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.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.EncryptionUtil; 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.DataValidationException;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import static org.thingsboard.server.common.data.CacheConstants.DEVICE_CREDENTIALS_CACHE; 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.validateId;
import static org.thingsboard.server.dao.service.Validator.validateString; import static org.thingsboard.server.dao.service.Validator.validateString;
@ -59,7 +58,7 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
private DeviceCredentialsDao deviceCredentialsDao; private DeviceCredentialsDao deviceCredentialsDao;
@Autowired @Autowired
private DeviceService deviceService; private DataValidator<DeviceCredentials> credentialsValidator;
@Override @Override
public DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId) { public DeviceCredentials findDeviceCredentialsByDeviceId(TenantId tenantId, DeviceId deviceId) {
@ -375,46 +374,4 @@ public class DeviceCredentialsServiceImpl extends AbstractEntityService implemen
deviceCredentialsDao.removeById(tenantId, deviceCredentials.getUuidId()); deviceCredentialsDao.removeById(tenantId, deviceCredentials.getUuidId());
} }
private DataValidator<DeviceCredentials> credentialsValidator =
new DataValidator<DeviceCredentials>() {
@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!");
}
}
};
} }

490
dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java

@ -15,83 +15,37 @@
*/ */
package org.thingsboard.server.dao.device; 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 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.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; 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.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileInfo;
import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType; 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.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; 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.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.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.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; 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.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; 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.Arrays;
import java.util.Collections; 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.Lock;
import java.util.concurrent.locks.ReentrantLock; 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.common.data.CacheConstants.DEVICE_PROFILE_CACHE;
import static org.thingsboard.server.dao.service.Validator.validateId; 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_ID = "Incorrect deviceProfileId ";
private static final String INCORRECT_DEVICE_PROFILE_NAME = "Incorrect deviceProfileName "; 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 @Autowired
private DeviceProfileDao deviceProfileDao; private DeviceProfileDao deviceProfileDao;
@ -126,20 +67,11 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
@Autowired @Autowired
private DeviceService deviceService; private DeviceService deviceService;
@Autowired
private TenantDao tenantDao;
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@Autowired @Autowired
private OtaPackageService otaPackageService; private DataValidator<DeviceProfile> deviceProfileValidator;
@Autowired
private RuleChainService ruleChainService;
@Autowired
private DashboardService dashboardService;
private final Lock findOrCreateLock = new ReentrantLock(); private final Lock findOrCreateLock = new ReentrantLock();
@ -359,426 +291,6 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D
tenantDeviceProfilesRemover.removeEntities(tenantId, tenantId); tenantDeviceProfilesRemover.removeEntities(tenantId, tenantId);
} }
private DataValidator<DeviceProfile> 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<LwM2MBootstrapServerCredential> lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap();
if (lwM2MBootstrapServersConfigurations != null) {
validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations,
((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable());
for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) {
validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential);
}
}
}
List<DeviceProfileAlarm> profileAlarms = deviceProfile.getProfileData().getAlarms();
if (!CollectionUtils.isEmpty(profileAlarms)) {
Set<String> 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<TypeElement> 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<FieldElement> 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<EnumElement> 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<MessageElement> 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<OneOfElement> 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<TypeElement> nestedTypes = messageElement.getNestedTypes();
if (!nestedTypes.isEmpty()) {
List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes);
if (!nestedEnumTypes.isEmpty()) {
checkEnumElements(schemaName, nestedEnumTypes);
}
List<MessageElement> nestedMessageTypes = getMessageTypes(nestedTypes);
checkMessageElements(schemaName, nestedMessageTypes);
}
});
}
}
private List<MessageElement> getMessageTypes(List<TypeElement> types) {
return types.stream()
.filter(typeElement -> typeElement instanceof MessageElement)
.map(typeElement -> (MessageElement) typeElement)
.collect(Collectors.toList());
}
private List<EnumElement> getEnumElements(List<TypeElement> 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<Descriptors.FieldDescriptor> 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<LwM2MBootstrapServerCredential> lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) {
Set<String> uris = new HashSet<>();
Set<Integer> 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<TenantId, DeviceProfile> tenantDeviceProfilesRemover = private PaginatedRemover<TenantId, DeviceProfile> tenantDeviceProfilesRemover =
new PaginatedRemover<TenantId, DeviceProfile>() { new PaginatedRemover<TenantId, DeviceProfile>() {

147
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.apache.commons.lang3.RandomStringUtils;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; 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.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching; import org.springframework.cache.annotation.Caching;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil; 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.Device;
import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceInfo;
import org.thingsboard.server.common.data.DeviceProfile; 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.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; 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.DeviceSearchQuery;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; 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.CoapDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; 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.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceData; 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.Lwm2mDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; 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.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
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.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionFailedException;
import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionRequest;
import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.exception.DataValidationException; 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.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; 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 javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; 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.common.data.CacheConstants.DEVICE_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs; 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.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@ -116,12 +103,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
@Autowired @Autowired
private DeviceDao deviceDao; private DeviceDao deviceDao;
@Autowired
private TenantDao tenantDao;
@Autowired
private CustomerDao customerDao;
@Autowired @Autowired
private DeviceCredentialsService deviceCredentialsService; private DeviceCredentialsService deviceCredentialsService;
@ -129,17 +110,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
private DeviceProfileService deviceProfileService; private DeviceProfileService deviceProfileService;
@Autowired @Autowired
private CacheManager cacheManager; private EntitiesCacheManager cacheManager;
@Autowired @Autowired
private EventService eventService; private EventService eventService;
@Autowired @Autowired
@Lazy private DataValidator<Device> deviceValidator;
private TbTenantProfileCache tenantProfileCache;
@Autowired
private OtaPackageService otaPackageService;
@Override @Override
public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) {
@ -274,8 +251,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); ConstraintViolationException e = extractConstraintViolationException(t).orElse(null);
if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { 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. // remove device from cache in case null value cached in the distributed redis.
removeDeviceFromCacheByName(device.getTenantId(), device.getName()); cacheManager.removeDeviceFromCacheByName(device.getTenantId(), device.getName());
removeDeviceFromCacheById(device.getTenantId(), device.getId()); cacheManager.removeDeviceFromCacheById(device.getTenantId(), device.getId());
throw new DataValidationException("Device with such name already exists!"); throw new DataValidationException("Device with such name already exists!");
} else { } else {
throw t; throw t;
@ -321,8 +298,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
Device device = findDeviceById(tenantId, deviceId); Device device = findDeviceById(tenantId, deviceId);
device.setCustomerId(customerId); device.setCustomerId(customerId);
Device savedDevice = saveDevice(device); Device savedDevice = saveDevice(device);
removeDeviceFromCacheByName(tenantId, device.getName()); cacheManager.removeDeviceFromCacheByName(tenantId, device.getName());
removeDeviceFromCacheById(tenantId, device.getId()); cacheManager.removeDeviceFromCacheById(tenantId, device.getId());
return savedDevice; return savedDevice;
} }
@ -331,8 +308,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
Device device = findDeviceById(tenantId, deviceId); Device device = findDeviceById(tenantId, deviceId);
device.setCustomerId(null); device.setCustomerId(null);
Device savedDevice = saveDevice(device); Device savedDevice = saveDevice(device);
removeDeviceFromCacheByName(tenantId, device.getName()); cacheManager.removeDeviceFromCacheByName(tenantId, device.getName());
removeDeviceFromCacheById(tenantId, device.getId()); cacheManager.removeDeviceFromCacheById(tenantId, device.getId());
return savedDevice; return savedDevice;
} }
@ -362,22 +339,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
deviceDao.removeById(tenantId, deviceId.getId()); deviceDao.removeById(tenantId, deviceId.getId());
removeDeviceFromCacheByName(tenantId, deviceName); cacheManager.removeDeviceFromCacheByName(tenantId, deviceName);
removeDeviceFromCacheById(tenantId, deviceId); 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 @Override
public PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<Device> 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 // explicitly remove device with previous tenant id from cache
// result device object will have different tenant id and will not remove entity from cache // result device object will have different tenant id and will not remove entity from cache
removeDeviceFromCacheByName(oldTenantId, device.getName()); cacheManager.removeDeviceFromCacheByName(oldTenantId, device.getName());
removeDeviceFromCacheById(oldTenantId, device.getId()); cacheManager.removeDeviceFromCacheById(oldTenantId, device.getId());
return savedDevice; return savedDevice;
} }
@ -639,7 +605,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); 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; return savedDevice;
} }
@ -710,91 +676,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
return deviceDao.countByTenantId(tenantId); return deviceDao.countByTenantId(tenantId);
} }
private DataValidator<Device> deviceValidator =
new DataValidator<Device>() {
@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<TenantId, Device> tenantDevicesRemover = private PaginatedRemover<TenantId, Device> tenantDevicesRemover =
new PaginatedRemover<TenantId, Device>() { new PaginatedRemover<TenantId, Device>() {

17
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.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
@Service @Service
@ -33,6 +32,9 @@ public class BaseEdgeEventService implements EdgeEventService {
@Autowired @Autowired
private EdgeEventDao edgeEventDao; private EdgeEventDao edgeEventDao;
@Autowired
private DataValidator<EdgeEvent> edgeEventValidator;
@Override @Override
public EdgeEvent save(EdgeEvent edgeEvent) { public EdgeEvent save(EdgeEvent edgeEvent) {
edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId); edgeEventValidator.validate(edgeEvent, EdgeEvent::getTenantId);
@ -48,17 +50,4 @@ public class BaseEdgeEventService implements EdgeEventService {
public void cleanupEvents(long ttl) { public void cleanupEvents(long ttl) {
edgeEventDao.cleanupEvents(ttl); edgeEventDao.cleanupEvents(ttl);
} }
private DataValidator<EdgeEvent> edgeEventValidator =
new DataValidator<EdgeEvent>() {
@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!");
}
}
};
} }

93
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 lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; 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.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service; 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.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; 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.User;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeInfo; 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.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; 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.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.relation.RelationService; 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.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; 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.common.data.CacheConstants.EDGE_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs; 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.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@ -95,17 +87,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Autowired @Autowired
private EdgeDao edgeDao; private EdgeDao edgeDao;
@Autowired
private TenantDao tenantDao;
@Autowired
private CustomerDao customerDao;
@Autowired @Autowired
private UserService userService; private UserService userService;
@Autowired @Autowired
private CacheManager cacheManager; private EntitiesCacheManager cacheManager;
@Autowired @Autowired
private RuleChainService ruleChainService; private RuleChainService ruleChainService;
@ -113,6 +99,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Autowired @Autowired
private RelationService relationService; private RelationService relationService;
@Autowired
private DataValidator<Edge> edgeValidator;
@Override @Override
public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) { public Edge findEdgeById(TenantId tenantId, EdgeId edgeId) {
log.trace("Executing findEdgeById [{}]", 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}") @CacheEvict(cacheNames = EDGE_CACHE, key = "{#edge.tenantId, #edge.name}")
@Override @Override
public Edge saveEdge(Edge edge, boolean doValidate) { public Edge saveEdge(Edge edge) {
log.trace("Executing saveEdge [{}]", edge); log.trace("Executing saveEdge [{}]", edge);
if (doValidate) { edgeValidator.validate(edge, Edge::getTenantId);
edgeValidator.validate(edge, Edge::getTenantId);
}
try { try {
return edgeDao.save(edge.getTenantId(), edge); return edgeDao.save(edge.getTenantId(), edge);
} catch (Exception t) { } catch (Exception t) {
@ -175,7 +162,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
log.trace("[{}] Executing assignEdgeToCustomer [{}][{}]", tenantId, edgeId, customerId); log.trace("[{}] Executing assignEdgeToCustomer [{}][{}]", tenantId, edgeId, customerId);
Edge edge = findEdgeById(tenantId, edgeId); Edge edge = findEdgeById(tenantId, edgeId);
edge.setCustomerId(customerId); edge.setCustomerId(customerId);
return saveEdge(edge, true); return saveEdge(edge);
} }
@Override @Override
@ -183,7 +170,7 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
log.trace("[{}] Executing unassignEdgeFromCustomer [{}]", tenantId, edgeId); log.trace("[{}] Executing unassignEdgeFromCustomer [{}]", tenantId, edgeId);
Edge edge = findEdgeById(tenantId, edgeId); Edge edge = findEdgeById(tenantId, edgeId);
edge.setCustomerId(null); edge.setCustomerId(null);
return saveEdge(edge, true); return saveEdge(edge);
} }
@Override @Override
@ -195,16 +182,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
deleteEntityRelations(tenantId, edgeId); deleteEntityRelations(tenantId, edgeId);
removeEdgeFromCacheByName(edge.getTenantId(), edge.getName()); cacheManager.removeEdgeFromCacheByName(edge.getTenantId(), edge.getName());
edgeDao.removeById(tenantId, edgeId.getId()); 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 @Override
public PageData<Edge> findEdgesByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<Edge> findEdgesByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findEdgesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); log.trace("Executing findEdgesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
@ -330,7 +312,9 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
@Nullable @Nullable
@Override @Override
public List<Edge> apply(@Nullable List<Edge> edgeList) { public List<Edge> apply(@Nullable List<Edge> 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()); }, MoreExecutors.directExecutor());
@ -375,57 +359,6 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
return edgeDao.findEdgesByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), entityId.getEntityType(), pageLink); return edgeDao.findEdgesByTenantIdAndEntityId(tenantId.getId(), entityId.getId(), entityId.getEntityType(), pageLink);
} }
private DataValidator<Edge> edgeValidator =
new DataValidator<Edge>() {
@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<TenantId, Edge> tenantEdgesRemover = private PaginatedRemover<TenantId, Edge> tenantEdgesRemover =
new PaginatedRemover<TenantId, Edge>() { new PaginatedRemover<TenantId, Edge>() {

67
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.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; 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.Cacheable;
import org.springframework.cache.annotation.Caching; import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service; 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.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo; 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.edge.Edge;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId; 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.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; 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.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
@ -65,7 +60,6 @@ import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE; 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.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString; import static org.thingsboard.server.dao.service.Validator.validateString;
@ -87,13 +81,10 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
private EntityViewDao entityViewDao; private EntityViewDao entityViewDao;
@Autowired @Autowired
private TenantDao tenantDao; private CacheManager cacheManager;
@Autowired
private CustomerDao customerDao;
@Autowired @Autowired
private CacheManager cacheManager; private DataValidator<EntityView> entityViewValidator;
@Caching(evict = { @Caching(evict = {
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"), @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) { public EntityView saveEntityView(EntityView entityView) {
log.trace("Executing save entity view [{}]", entityView); log.trace("Executing save entity view [{}]", entityView);
entityViewValidator.validate(entityView, EntityView::getTenantId); entityViewValidator.validate(entityView, EntityView::getTenantId);
EntityView savedEntityView = entityViewDao.save(entityView.getTenantId(), entityView); return entityViewDao.save(entityView.getTenantId(), entityView);
return savedEntityView;
} }
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityViewId}") @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); return entityViewDao.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
} }
private DataValidator<EntityView> entityViewValidator =
new DataValidator<EntityView>() {
@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<TenantId, EntityView> tenantEntityViewRemover = new PaginatedRemover<TenantId, EntityView>() { private PaginatedRemover<TenantId, EntityView> tenantEntityViewRemover = new PaginatedRemover<TenantId, EntityView>() {
@Override @Override
protected PageData<EntityView> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { protected PageData<EntityView> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {

19
dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java

@ -46,6 +46,9 @@ public class BaseEventService implements EventService {
@Autowired @Autowired
public EventDao eventDao; public EventDao eventDao;
@Autowired
private DataValidator<Event> eventValidator;
@Override @Override
public ListenableFuture<Void> saveAsync(Event event) { public ListenableFuture<Void> saveAsync(Event event) {
eventValidator.validate(event, Event::getTenantId); 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) { public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) {
eventDao.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs); eventDao.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
} }
private DataValidator<Event> eventValidator =
new DataValidator<Event>() {
@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!.");
}
}
};
} }

44
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ConfigTemplateServiceImpl.java

@ -15,15 +15,13 @@
*/ */
package org.thingsboard.server.dao.oauth2; package org.thingsboard.server.dao.oauth2;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 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.OAuth2ClientRegistrationTemplateId;
import org.thingsboard.server.common.data.id.TenantId; 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.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
@ -36,12 +34,14 @@ import static org.thingsboard.server.dao.service.Validator.validateString;
@Slf4j @Slf4j
@Service @Service
@AllArgsConstructor
public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService implements OAuth2ConfigTemplateService { 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 static final String INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID = "Incorrect clientRegistrationTemplateId ";
private OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao; private static final String INCORRECT_CLIENT_REGISTRATION_PROVIDER_ID = "Incorrect clientRegistrationProviderId ";
private final OAuth2ClientRegistrationTemplateDao clientRegistrationTemplateDao;
private final DataValidator<OAuth2ClientRegistrationTemplate> clientRegistrationTemplateValidator;
@Override @Override
public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) { public OAuth2ClientRegistrationTemplate saveClientRegistrationTemplate(OAuth2ClientRegistrationTemplate clientRegistrationTemplate) {
@ -87,32 +87,4 @@ public class OAuth2ConfigTemplateServiceImpl extends AbstractEntityService imple
validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId); validateId(templateId, INCORRECT_CLIENT_REGISTRATION_TEMPLATE_ID + templateId);
clientRegistrationTemplateDao.removeById(TenantId.SYS_TENANT_ID, templateId.getId()); clientRegistrationTemplateDao.removeById(TenantId.SYS_TENANT_ID, templateId.getId());
} }
private final DataValidator<OAuth2ClientRegistrationTemplate> clientRegistrationTemplateValidator =
new DataValidator<OAuth2ClientRegistrationTemplate>() {
@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!");
}
}
};
} }

180
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.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.ota.OtaPackageDataCache; 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.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils; 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.DeviceProfileId;
import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.TenantId; 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.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; 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.nio.ByteBuffer;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE; 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.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink; 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_OTA_PACKAGE_ID = "Incorrect otaPackageId ";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
private final TenantDao tenantDao;
private final DeviceProfileDao deviceProfileDao;
private final OtaPackageDao otaPackageDao; private final OtaPackageDao otaPackageDao;
private final OtaPackageInfoDao otaPackageInfoDao; private final OtaPackageInfoDao otaPackageInfoDao;
private final CacheManager cacheManager; private final CacheManager cacheManager;
private final OtaPackageDataCache otaPackageDataCache; private final OtaPackageDataCache otaPackageDataCache;
private final DataValidator<OtaPackageInfo> otaPackageInfoValidator;
@Autowired private final DataValidator<OtaPackage> otaPackageValidator;
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl) { public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo, boolean isUrl) {
@ -230,168 +216,6 @@ public class BaseOtaPackageService implements OtaPackageService {
tenantOtaPackageRemover.removeEntities(tenantId, tenantId); tenantOtaPackageRemover.removeEntities(tenantId, tenantId);
} }
private DataValidator<OtaPackageInfo> 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<OtaPackage> 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<TenantId, OtaPackageInfo> tenantOtaPackageRemover = private PaginatedRemover<TenantId, OtaPackageInfo> tenantOtaPackageRemover =
new PaginatedRemover<>() { new PaginatedRemover<>() {

60
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -16,51 +16,37 @@
package org.thingsboard.server.dao.resource; package org.thingsboard.server.dao.resource;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo; 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.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; 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.List;
import java.util.Optional; 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.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
@Service @Service
@Slf4j @Slf4j
@AllArgsConstructor
public class BaseResourceService implements ResourceService { public class BaseResourceService implements ResourceService {
public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId "; public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId ";
private final TbResourceDao resourceDao; private final TbResourceDao resourceDao;
private final TbResourceInfoDao resourceInfoDao; private final TbResourceInfoDao resourceInfoDao;
private final TenantDao tenantDao; private final DataValidator<TbResource> resourceValidator;
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;
}
@Override @Override
public TbResource saveResource(TbResource resource) { public TbResource saveResource(TbResource resource) {
@ -153,45 +139,7 @@ public class BaseResourceService implements ResourceService {
return resourceDao.sumDataSizeByTenantId(tenantId); return resourceDao.sumDataSizeByTenantId(tenantId);
} }
private DataValidator<TbResource> resourceValidator = new DataValidator<>() { private final PaginatedRemover<TenantId, TbResource> tenantResourcesRemover =
@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<TenantId, TbResource> tenantResourcesRemover =
new PaginatedRemover<>() { new PaginatedRemover<>() {
@Override @Override

107
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 com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; 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.BaseData;
import org.thingsboard.server.common.data.EntityType; 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.edge.Edge;
import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; 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.RuleChainUpdateResult;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult; 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.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; 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.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -94,11 +89,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
private RuleNodeDao ruleNodeDao; private RuleNodeDao ruleNodeDao;
@Autowired @Autowired
private TenantDao tenantDao; private DataValidator<RuleChain> ruleChainValidator;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
@Transactional @Transactional
@ -209,11 +200,37 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
} }
if (ruleChainMetaData.getRuleChainConnections() != null) { if (ruleChainMetaData.getRuleChainConnections() != null) {
for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { 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 from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId();
EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); sourceRuleNodeToTargetRuleNode.setFrom(from);
String type = nodeToRuleChainConnection.getType(); sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId());
createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); 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); int toIndex = ruleNodeIndexMap.get(toNodeId);
ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type); ruleChainMetaData.addConnectionInfo(fromIndex, toIndex, type);
} else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) { } else if (nodeRelation.getTo().getEntityType() == EntityType.RULE_CHAIN) {
RuleChainId targetRuleChainId = new RuleChainId(nodeRelation.getTo().getId()); log.warn("[{}][{}] Unsupported node relation: {}", tenantId, ruleChainId, nodeRelation.getTo());
ruleChainMetaData.addRuleChainConnectionInfo(fromIndex, targetRuleChainId, type, nodeRelation.getAdditionalInfo());
} }
} }
} }
@ -519,23 +535,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
} }
} }
private List<RuleChain> findAllTenantRuleChains(TenantId tenantId, RuleChainType type) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
return findAllTenantRuleChainsRecursive(tenantId, new ArrayList<>(), type, pageLink);
}
private List<RuleChain> findAllTenantRuleChainsRecursive(TenantId tenantId, List<RuleChain> accumulator, RuleChainType type, PageLink pageLink) {
PageData<RuleChain> persistentRuleChainData = findTenantRuleChainsByType(tenantId, type, pageLink);
List<RuleChain> 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<RuleChainMetaData> metadata, RuleChainId oldRuleChainId, RuleChainId newRuleChainId) { private void setNewRuleChainId(RuleChain ruleChain, List<RuleChainMetaData> metadata, RuleChainId oldRuleChainId, RuleChainId newRuleChainId) {
ruleChain.setId(newRuleChainId); ruleChain.setId(newRuleChainId);
for (RuleChainMetaData metaData : metadata) { for (RuleChainMetaData metaData : metadata) {
@ -705,46 +704,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
ruleNodeDao.removeById(tenantId, entityId.getId()); ruleNodeDao.removeById(tenantId, entityId.getId());
} }
private final DataValidator<RuleChain> 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<TenantId, RuleChain> tenantRuleChainsRemover = private final PaginatedRemover<TenantId, RuleChain> tenantRuleChainsRemover =
new PaginatedRemover<>() { new PaginatedRemover<>() {

61
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<AdminSettings> {
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!");
}
}
}

57
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<Alarm> {
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!");
}
}
}
}

50
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<ApiUsageState> {
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!");
}
}
}

108
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<Asset> {
@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!");
}
}
}
}

39
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<AuditLog> {
@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!");
}
}
}

123
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<D extends BaseData<?>> extends DataValidator<D> {
@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!");
}
}
}

51
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<OAuth2ClientRegistrationTemplate> {
@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!");
}
}
}

43
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<ComponentDescriptor> {
@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!");
}
}
}

92
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<Customer> {
@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!");
}
}
}
}

68
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<Dashboard> {
@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!");
}
}
}
}

75
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<DeviceCredentials> {
@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!");
}
}
}

147
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<Device> {
@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!");
}
}
}
}

524
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<DeviceProfile> {
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<LwM2MBootstrapServerCredential> lwM2MBootstrapServersConfigurations = ((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).getBootstrap();
if (lwM2MBootstrapServersConfigurations != null) {
validateLwm2mServersConfigOfBootstrapForClient(lwM2MBootstrapServersConfigurations,
((Lwm2mDeviceProfileTransportConfiguration) transportConfiguration).isBootstrapServerUpdateEnable());
for (LwM2MBootstrapServerCredential bootstrapServerCredential : lwM2MBootstrapServersConfigurations) {
validateLwm2mServersCredentialOfBootstrapForClient(bootstrapServerCredential);
}
}
}
List<DeviceProfileAlarm> profileAlarms = deviceProfile.getProfileData().getAlarms();
if (!CollectionUtils.isEmpty(profileAlarms)) {
Set<String> 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<TypeElement> 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<FieldElement> 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<EnumElement> 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<MessageElement> 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<OneOfElement> 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<TypeElement> nestedTypes = messageElement.getNestedTypes();
if (!nestedTypes.isEmpty()) {
List<EnumElement> nestedEnumTypes = getEnumElements(nestedTypes);
if (!nestedEnumTypes.isEmpty()) {
checkEnumElements(schemaName, nestedEnumTypes);
}
List<MessageElement> nestedMessageTypes = getMessageTypes(nestedTypes);
checkMessageElements(schemaName, nestedMessageTypes);
}
});
}
}
private List<MessageElement> getMessageTypes(List<TypeElement> types) {
return types.stream()
.filter(typeElement -> typeElement instanceof MessageElement)
.map(typeElement -> (MessageElement) typeElement)
.collect(Collectors.toList());
}
private List<EnumElement> getEnumElements(List<TypeElement> 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<Descriptors.FieldDescriptor> 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<LwM2MBootstrapServerCredential> lwM2MBootstrapServersConfigurations, boolean isBootstrapServerUpdateEnable) {
Set<String> uris = new HashSet<>();
Set<Integer> 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;
}
}
}

90
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<Edge> {
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!");
}
}
}
}

36
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<EdgeEvent> {
@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!");
}
}
}

88
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<EntityView> {
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!");
}
}
}
}

40
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<Event> {
@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!.");
}
}
}

105
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<OtaPackage> {
@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);
}
}
}

40
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<OtaPackageInfo> {
@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);
}
}

82
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<TbResource> {
@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!");
}
}
}
}

88
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<RuleChain> {
@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!");
}
}
}
}

68
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<Tenant> {
@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!");
}
}
}

69
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<TenantProfile> {
@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!");
}
}
}

68
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<UserCredentials> {
@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!");
}
}
}

136
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<User> {
@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!");
}
}
}
}

98
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<WidgetTypeDetails> {
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!");
}
}
}

81
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<WidgetsBundle> {
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!");
}
}
}

38
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 com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.AdminSettingsId;
import org.thingsboard.server.common.data.id.TenantId; 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.DataValidator;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
@ -34,6 +32,9 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
@Autowired @Autowired
private AdminSettingsDao adminSettingsDao; private AdminSettingsDao adminSettingsDao;
@Autowired
private DataValidator<AdminSettings> adminSettingsValidator;
@Override @Override
public AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId) { public AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId) {
log.trace("Executing findAdminSettingsById [{}]", adminSettingsId); log.trace("Executing findAdminSettingsById [{}]", adminSettingsId);
@ -62,37 +63,4 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
return adminSettingsDao.save(tenantId, adminSettings); return adminSettingsDao.save(tenantId, adminSettings);
} }
private DataValidator<AdminSettings> adminSettingsValidator =
new DataValidator<AdminSettings>() {
@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!");
}
}
};
} }

50
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java

@ -16,7 +16,6 @@
package org.thingsboard.server.dao.tenant; package org.thingsboard.server.dao.tenant;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache; import org.springframework.cache.Cache;
@ -55,6 +54,9 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
@Autowired @Autowired
private CacheManager cacheManager; private CacheManager cacheManager;
@Autowired
private DataValidator<TenantProfile> tenantProfileValidator;
@Cacheable(cacheNames = TENANT_PROFILE_CACHE, key = "{#tenantProfileId.id}") @Cacheable(cacheNames = TENANT_PROFILE_CACHE, key = "{#tenantProfileId.id}")
@Override @Override
public TenantProfile findTenantProfileById(TenantId tenantId, TenantProfileId tenantProfileId) { public TenantProfile findTenantProfileById(TenantId tenantId, TenantProfileId tenantProfileId) {
@ -104,10 +106,10 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
if (tenantProfile != null && tenantProfile.isDefault()) { if (tenantProfile != null && tenantProfile.isDefault()) {
throw new DataValidationException("Deletion of Default Tenant Profile is prohibited!"); 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 { try {
tenantProfileDao.removeById(tenantId, tenantProfileId.getId()); tenantProfileDao.removeById(tenantId, tenantProfileId.getId());
} catch (Exception t) { } catch (Exception t) {
@ -122,6 +124,10 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
Cache cache = cacheManager.getCache(TENANT_PROFILE_CACHE); Cache cache = cacheManager.getCache(TENANT_PROFILE_CACHE);
cache.evict(Collections.singletonList(tenantProfileId.getId())); cache.evict(Collections.singletonList(tenantProfileId.getId()));
cache.evict(Arrays.asList("info", tenantProfileId.getId())); cache.evict(Arrays.asList("info", tenantProfileId.getId()));
if (isDefault) {
cache.evict(Collections.singletonList("default"));
cache.evict(Arrays.asList("default", "info"));
}
} }
@Override @Override
@ -209,41 +215,7 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
tenantProfilesRemover.removeEntities(tenantId, null); tenantProfilesRemover.removeEntities(tenantId, null);
} }
private DataValidator<TenantProfile> tenantProfileValidator = private final PaginatedRemover<String, TenantProfile> tenantProfilesRemover =
new DataValidator<TenantProfile>() {
@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<String, TenantProfile> tenantProfilesRemover =
new PaginatedRemover<String, TenantProfile>() { new PaginatedRemover<String, TenantProfile>() {
@Override @Override
@ -253,7 +225,7 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T
@Override @Override
protected void removeEntity(TenantId tenantId, TenantProfile entity) { protected void removeEntity(TenantId tenantId, TenantProfile entity) {
removeTenantProfile(tenantId, entity.getId()); removeTenantProfile(tenantId, entity.getId(), entity.isDefault());
} }
}; };

39
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 com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo; 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.device.DeviceService;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; 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.ota.OtaPackageService;
import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rpc.RpcService; 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"; private static final String DEFAULT_TENANT_REGION = "Global";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
@Value("${zk.enabled}")
private Boolean zkEnabled;
@Autowired @Autowired
private TenantDao tenantDao; private TenantDao tenantDao;
@ -103,6 +97,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
@Autowired @Autowired
private RpcService rpcService; private RpcService rpcService;
@Autowired
private DataValidator<Tenant> tenantValidator;
@Override @Override
public Tenant findTenantById(TenantId tenantId) { public Tenant findTenantById(TenantId tenantId) {
log.trace("Executing findTenantById [{}]", 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); tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID);
} }
private DataValidator<Tenant> tenantValidator =
new DataValidator<Tenant>() {
@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<TenantId, Tenant> tenantsRemover = private PaginatedRemover<TenantId, Tenant> tenantsRemover =
new PaginatedRemover<>() { new PaginatedRemover<>() {

31
dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.dao.usagerecord; package org.thingsboard.server.dao.usagerecord;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ApiFeature; 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.kv.TsKvEntry;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService; 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.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.server.dao.tenant.TenantProfileDao; import org.thingsboard.server.dao.tenant.TenantProfileDao;
@ -47,6 +47,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Service @Service
@Slf4j @Slf4j
@AllArgsConstructor
public class ApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { public class ApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; 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 TenantProfileDao tenantProfileDao;
private final TenantDao tenantDao; private final TenantDao tenantDao;
private final TimeseriesService tsService; private final TimeseriesService tsService;
private final DataValidator<ApiUsageState> apiUsageStateValidator;
public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao, TimeseriesService tsService) {
this.tenantDao = tenantDao;
this.apiUsageStateDao = apiUsageStateDao;
this.tenantProfileDao = tenantProfileDao;
this.tsService = tsService;
}
@Override @Override
public void deleteApiUsageStateByTenantId(TenantId tenantId) { public void deleteApiUsageStateByTenantId(TenantId tenantId) {
@ -157,24 +152,4 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
return apiUsageStateDao.findById(tenantId, id.getId()); return apiUsageStateDao.findById(tenantId, id.getId());
} }
private DataValidator<ApiUsageState> apiUsageStateValidator =
new DataValidator<ApiUsageState>() {
@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!");
}
}
};
} }

141
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.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; 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.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId; 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.id.UserId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; 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.UserCredentials;
import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; 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.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; 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.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; 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.HashMap;
import java.util.Map; import java.util.Map;
@ -80,22 +69,19 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
private final UserDao userDao; private final UserDao userDao;
private final UserCredentialsDao userCredentialsDao; private final UserCredentialsDao userCredentialsDao;
private final TenantDao tenantDao; private final DataValidator<User> userValidator;
private final CustomerDao customerDao; private final DataValidator<UserCredentials> userCredentialsValidator;
private final TbTenantProfileCache tenantProfileCache;
private final ApplicationEventPublisher eventPublisher; private final ApplicationEventPublisher eventPublisher;
public UserServiceImpl(UserDao userDao, public UserServiceImpl(UserDao userDao,
UserCredentialsDao userCredentialsDao, UserCredentialsDao userCredentialsDao,
TenantDao tenantDao, DataValidator<User> userValidator,
CustomerDao customerDao, DataValidator<UserCredentials> userCredentialsValidator,
@Lazy TbTenantProfileCache tenantProfileCache,
ApplicationEventPublisher eventPublisher) { ApplicationEventPublisher eventPublisher) {
this.userDao = userDao; this.userDao = userDao;
this.userCredentialsDao = userCredentialsDao; this.userCredentialsDao = userCredentialsDao;
this.tenantDao = tenantDao; this.userValidator = userValidator;
this.customerDao = customerDao; this.userCredentialsValidator = userCredentialsValidator;
this.tenantProfileCache = tenantProfileCache;
this.eventPublisher = eventPublisher; this.eventPublisher = eventPublisher;
} }
@ -383,119 +369,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
saveUser(user); saveUser(user);
} }
private final DataValidator<User> 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<UserCredentials> 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<TenantId, User> tenantAdminsRemover = new PaginatedRemover<>() { private final PaginatedRemover<TenantId, User> tenantAdminsRemover = new PaginatedRemover<>() {
@Override @Override
protected PageData<User> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { protected PageData<User> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {

71
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java

@ -16,21 +16,15 @@
package org.thingsboard.server.dao.widget; package org.thingsboard.server.dao.widget;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 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.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo; 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.DataValidator;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.List; import java.util.List;
@ -44,10 +38,7 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
private WidgetTypeDao widgetTypeDao; private WidgetTypeDao widgetTypeDao;
@Autowired @Autowired
private TenantDao tenantDao; private DataValidator<WidgetTypeDetails> widgetTypeValidator;
@Autowired
private WidgetsBundleDao widgetsBundleService;
@Override @Override
public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) { public WidgetType findWidgetTypeById(TenantId tenantId, WidgetTypeId widgetTypeId) {
@ -121,64 +112,4 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
} }
} }
private DataValidator<WidgetTypeDetails> 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!");
}
}
};
} }

59
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java

@ -16,22 +16,17 @@
package org.thingsboard.server.dao.widget; package org.thingsboard.server.dao.widget;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; 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.TenantId;
import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.id.WidgetsBundleId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.widget.WidgetsBundle; 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.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -48,10 +43,10 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
private WidgetsBundleDao widgetsBundleDao; private WidgetsBundleDao widgetsBundleDao;
@Autowired @Autowired
private TenantDao tenantDao; private WidgetTypeService widgetTypeService;
@Autowired @Autowired
private WidgetTypeService widgetTypeService; private DataValidator<WidgetsBundle> widgetsBundleValidator;
@Override @Override
public WidgetsBundle findWidgetsBundleById(TenantId tenantId, WidgetsBundleId widgetsBundleId) { public WidgetsBundle findWidgetsBundleById(TenantId tenantId, WidgetsBundleId widgetsBundleId) {
@ -150,56 +145,6 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService {
tenantWidgetsBundleRemover.removeEntities(tenantId, tenantId); tenantWidgetsBundleRemover.removeEntities(tenantId, tenantId);
} }
private DataValidator<WidgetsBundle> widgetsBundleValidator =
new DataValidator<WidgetsBundle>() {
@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<TenantId, WidgetsBundle> tenantWidgetsBundleRemover = private PaginatedRemover<TenantId, WidgetsBundle> tenantWidgetsBundleRemover =
new PaginatedRemover<TenantId, WidgetsBundle>() { new PaginatedRemover<TenantId, WidgetsBundle>() {

6
dao/src/main/resources/sql/schema-entities.sql

@ -213,7 +213,6 @@ CREATE TABLE IF NOT EXISTS ota_package (
additional_info varchar, additional_info varchar,
search_text varchar(255), search_text varchar(255),
CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
-- CONSTRAINT fk_device_profile_firmware FOREIGN KEY (device_profile_id) REFERENCES device_profile(id) ON DELETE CASCADE
); );
CREATE TABLE IF NOT EXISTS device_profile ( 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) 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 -- 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 ( -- CREATE TABLE IF NOT EXISTS device_profile_firmware (
-- device_profile_id uuid NOT NULL, -- device_profile_id uuid NOT NULL,

22
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.DeviceTransportType;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Event; 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.Tenant;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; 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.DefaultDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.edge.Edge; 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.EntityId;
import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId; 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.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.audit.AuditLogLevelFilter; 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 org.thingsboard.server.dao.widget.WidgetsBundleService;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -251,4 +256,21 @@ public abstract class AbstractServiceTest {
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
return edge; 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;
}
} }

2
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.setType("default");
edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
edge = edgeService.saveEdge(edge, true); edge = edgeService.saveEdge(edge);
try { try {
dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId()); dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId());
} finally { } finally {

12
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceCredentialsCacheTest.java

@ -49,6 +49,9 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
@Autowired @Autowired
private DeviceCredentialsService deviceCredentialsService; private DeviceCredentialsService deviceCredentialsService;
@Autowired
private DataValidator<DeviceCredentials> credentialsValidator;
private DeviceCredentialsDao deviceCredentialsDao; private DeviceCredentialsDao deviceCredentialsDao;
private DeviceService deviceService; private DeviceService deviceService;
@ -59,10 +62,15 @@ public abstract class BaseDeviceCredentialsCacheTest extends AbstractServiceTest
@Before @Before
public void setup() throws Exception { public void setup() throws Exception {
deviceCredentialsDao = mock(DeviceCredentialsDao.class);
deviceService = mock(DeviceService.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(), "deviceCredentialsDao", deviceCredentialsDao);
ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "deviceService", deviceService); ReflectionTestUtils.setField(unwrapDeviceCredentialsService(), "credentialsValidator", credentialsValidator);
} }
@After @After

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save