diff --git a/application/pom.xml b/application/pom.xml index 2d6dda0bbe..6d1e009d00 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -69,6 +69,10 @@ org.thingsboard.common cluster-api + + org.thingsboard.common + version-control + org.thingsboard.rule-engine rule-engine-components diff --git a/application/src/main/conf/logback.xml b/application/src/main/conf/logback.xml index 898128c1ff..284af71953 100644 --- a/application/src/main/conf/logback.xml +++ b/application/src/main/conf/logback.xml @@ -36,6 +36,8 @@ + + diff --git a/application/src/main/data/upgrade/3.3.4/schema_update.sql b/application/src/main/data/upgrade/3.3.4/schema_update.sql index 7584d2f732..c86a4fe1f4 100644 --- a/application/src/main/data/upgrade/3.3.4/schema_update.sql +++ b/application/src/main/data/upgrade/3.3.4/schema_update.sql @@ -14,6 +14,31 @@ -- limitations under the License. -- +ALTER TABLE device + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE device_profile + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE asset + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE rule_chain + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE rule_node + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE dashboard + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE customer + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE widgets_bundle + ADD COLUMN IF NOT EXISTS external_id UUID; +ALTER TABLE entity_view + ADD COLUMN IF NOT EXISTS external_id UUID; + +CREATE INDEX IF NOT EXISTS idx_rule_node_external_id ON rule_node(rule_chain_id, external_id); +CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type); + +ALTER TABLE admin_settings + ADD COLUMN IF NOT EXISTS tenant_id uuid NOT NULL DEFAULT '13814000-1dd2-11b2-8080-808080808080'; + CREATE TABLE IF NOT EXISTS queue ( id uuid NOT NULL CONSTRAINT queue_pkey PRIMARY KEY, created_time bigint NOT NULL, @@ -35,3 +60,81 @@ CREATE TABLE IF NOT EXISTS user_auth_settings ( user_id uuid UNIQUE NOT NULL CONSTRAINT fk_user_auth_settings_user_id REFERENCES tb_user(id), two_fa_settings varchar ); + +CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id); + +ALTER TABLE tenant_profile DROP COLUMN IF EXISTS isolated_tb_core; + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'device_external_id_unq_key') THEN + ALTER TABLE device ADD CONSTRAINT device_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'device_profile_external_id_unq_key') THEN + ALTER TABLE device_profile ADD CONSTRAINT device_profile_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'asset_external_id_unq_key') THEN + ALTER TABLE asset ADD CONSTRAINT asset_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'rule_chain_external_id_unq_key') THEN + ALTER TABLE rule_chain ADD CONSTRAINT rule_chain_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'dashboard_external_id_unq_key') THEN + ALTER TABLE dashboard ADD CONSTRAINT dashboard_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'customer_external_id_unq_key') THEN + ALTER TABLE customer ADD CONSTRAINT customer_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'widgets_bundle_external_id_unq_key') THEN + ALTER TABLE widgets_bundle ADD CONSTRAINT widgets_bundle_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + +DO +$$ + BEGIN + IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'entity_view_external_id_unq_key') THEN + ALTER TABLE entity_view ADD CONSTRAINT entity_view_external_id_unq_key UNIQUE (tenant_id, external_id); + END IF; + END; +$$; + diff --git a/application/src/main/data/upgrade/3.3.4/schema_update_device_profile.sql b/application/src/main/data/upgrade/3.3.4/schema_update_device_profile.sql deleted file mode 100644 index e0b0cc7f1a..0000000000 --- a/application/src/main/data/upgrade/3.3.4/schema_update_device_profile.sql +++ /dev/null @@ -1,49 +0,0 @@ --- --- Copyright © 2016-2022 The Thingsboard Authors --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- - -ALTER TABLE device_profile - ADD COLUMN IF NOT EXISTS default_queue_id uuid; - -DO -$$ - BEGIN - IF EXISTS - (SELECT column_name - FROM information_schema.columns - WHERE table_name = 'device_profile' - AND column_name = 'default_queue_name' - ) - THEN - UPDATE device_profile - SET default_queue_id = q.id - FROM queue as q - WHERE default_queue_name = q.name; - END IF; - END -$$; - -DO -$$ - BEGIN - IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_default_queue_device_profile') THEN - ALTER TABLE device_profile - ADD CONSTRAINT fk_default_queue_device_profile FOREIGN KEY (default_queue_id) REFERENCES queue (id); - END IF; - END; -$$; - -ALTER TABLE device_profile - DROP COLUMN IF EXISTS default_queue_name; diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java index efc1e55701..e90ed98351 100644 --- a/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java +++ b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java @@ -32,7 +32,9 @@ import java.util.Arrays; "org.thingsboard.server.dao", "org.thingsboard.server.common.stats", "org.thingsboard.server.common.transport.config.ssl", - "org.thingsboard.server.cache"}) + "org.thingsboard.server.cache", + "org.thingsboard.server.springfox" +}) public class ThingsboardInstallApplication { private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 500ad60524..a38de7b839 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -48,7 +48,6 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.tools.TbRateLimits; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; @@ -77,9 +76,11 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; +import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; @@ -220,6 +221,11 @@ public class ActorSystemContext { @Getter private EntityViewService entityViewService; + @Lazy + @Autowired(required = false) + @Getter + private TbEntityViewService tbEntityViewService; + @Autowired @Getter private TelemetrySubscriptionService tsSubService; @@ -502,13 +508,8 @@ public class ActorSystemContext { return partitionService.resolve(serviceType, tenantId, entityId); } - public TopicPartitionInfo resolve(ServiceType serviceType, QueueId queueId, TenantId tenantId, EntityId entityId) { - return partitionService.resolve(serviceType, queueId, tenantId, entityId); - } - - @Deprecated public TopicPartitionInfo resolve(ServiceType serviceType, String queueName, TenantId tenantId, EntityId entityId) { - return partitionService.resolve(serviceType, tenantId, entityId, queueName); + return partitionService.resolve(serviceType, queueName, tenantId, entityId); } public String getServiceId() { diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 3538800d38..d5460689b8 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -162,18 +162,11 @@ class DefaultTbContext implements TbContext { } @Override - @Deprecated public void enqueue(TbMsg tbMsg, String queueName, Runnable onSuccess, Consumer onFailure) { TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName); enqueue(tpi, tbMsg, onFailure, onSuccess); } - @Override - public void enqueue(TbMsg tbMsg, QueueId queueId, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = resolvePartition(tbMsg, queueId); - enqueue(tpi, tbMsg, onFailure, onSuccess); - } - private void enqueue(TopicPartitionInfo tpi, TbMsg tbMsg, Consumer onFailure, Runnable onSuccess) { if (!tbMsg.isValid()) { log.trace("[{}] Skip invalid message: {}", getTenantId(), tbMsg); @@ -223,35 +216,30 @@ class DefaultTbContext implements TbContext { } @Override - public void enqueueForTellNext(TbMsg tbMsg, QueueId queueId, String relationType, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = resolvePartition(tbMsg, queueId); - enqueueForTellNext(tpi, queueId, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure); + public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer onFailure) { + TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName); + enqueueForTellNext(tpi, queueName, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure); } @Override - public void enqueueForTellNext(TbMsg tbMsg, QueueId queueId, Set relationTypes, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = resolvePartition(tbMsg, queueId); - enqueueForTellNext(tpi, queueId, tbMsg, relationTypes, null, onSuccess, onFailure); - } - - private TopicPartitionInfo resolvePartition(TbMsg tbMsg, QueueId queueId) { - return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueId, getTenantId(), tbMsg.getOriginator()); + public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set relationTypes, Runnable onSuccess, Consumer onFailure) { + TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName); + enqueueForTellNext(tpi, queueName, tbMsg, relationTypes, null, onSuccess, onFailure); } - @Deprecated private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) { return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); } private TopicPartitionInfo resolvePartition(TbMsg tbMsg) { - return resolvePartition(tbMsg, tbMsg.getQueueId()); + return resolvePartition(tbMsg, tbMsg.getQueueName()); } private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set relationTypes, String failureMessage, Runnable onSuccess, Consumer onFailure) { - enqueueForTellNext(tpi, source.getQueueId(), source, relationTypes, failureMessage, onSuccess, onFailure); + enqueueForTellNext(tpi, source.getQueueName(), source, relationTypes, failureMessage, onSuccess, onFailure); } - private void enqueueForTellNext(TopicPartitionInfo tpi, QueueId queueId, TbMsg source, Set relationTypes, String failureMessage, Runnable onSuccess, Consumer onFailure) { + private void enqueueForTellNext(TopicPartitionInfo tpi, String queueName, TbMsg source, Set relationTypes, String failureMessage, Runnable onSuccess, Consumer onFailure) { if (!source.isValid()) { log.trace("[{}] Skip invalid message: {}", getTenantId(), source); if (onFailure != null) { @@ -261,7 +249,7 @@ class DefaultTbContext implements TbContext { } RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId(); RuleNodeId ruleNodeId = nodeCtx.getSelf().getId(); - TbMsg tbMsg = TbMsg.newMsg(source, queueId, ruleChainId, ruleNodeId); + TbMsg tbMsg = TbMsg.newMsg(source, queueName, ruleChainId, ruleNodeId); TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder() .setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) @@ -320,13 +308,13 @@ class DefaultTbContext implements TbContext { } @Override - public TbMsg newMsg(QueueId queueId, String type, EntityId originator, TbMsgMetaData metaData, String data) { - return newMsg(queueId, type, originator, null, metaData, data); + public TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(queueName, type, originator, null, metaData, data); } @Override - public TbMsg newMsg(QueueId queueId, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { - return TbMsg.newMsg(queueId, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); + public TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); } @Override @@ -340,17 +328,17 @@ class DefaultTbContext implements TbContext { public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { RuleChainId ruleChainId = null; - QueueId queueId = null; + String queueName = null; if (device.getDeviceProfileId() != null) { DeviceProfile deviceProfile = mainCtx.getDeviceProfileCache().find(device.getDeviceProfileId()); if (deviceProfile == null) { log.warn("[{}] Device profile is null!", device.getDeviceProfileId()); } else { ruleChainId = deviceProfile.getDefaultRuleChainId(); - queueId = deviceProfile.getDefaultQueueId(); + queueName = deviceProfile.getDefaultQueueName(); } } - return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, queueId, ruleChainId); + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED, queueName, ruleChainId); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { @@ -359,7 +347,7 @@ class DefaultTbContext implements TbContext { public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { RuleChainId ruleChainId = null; - QueueId queueId = null; + String queueName = null; if (EntityType.DEVICE.equals(alarm.getOriginator().getEntityType())) { DeviceId deviceId = new DeviceId(alarm.getOriginator().getId()); DeviceProfile deviceProfile = mainCtx.getDeviceProfileCache().get(getTenantId(), deviceId); @@ -367,10 +355,10 @@ class DefaultTbContext implements TbContext { log.warn("[{}] Device profile is null!", deviceId); } else { ruleChainId = deviceProfile.getDefaultRuleChainId(); - queueId = deviceProfile.getDefaultQueueId(); + queueName = deviceProfile.getDefaultQueueName(); } } - return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action, queueId, ruleChainId); + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action, queueName, ruleChainId); } @Override @@ -382,9 +370,9 @@ class DefaultTbContext implements TbContext { return entityActionMsg(entity, id, ruleNodeId, action, null, null); } - public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, QueueId queueId, RuleChainId ruleChainId) { + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action, String queueName, RuleChainId ruleChainId) { try { - return TbMsg.newMsg(queueId, action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)), ruleChainId, null); + return TbMsg.newMsg(queueName, action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)), ruleChainId, null); } catch (JsonProcessingException | IllegalArgumentException e) { throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index add500f058..b4911650f3 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -293,7 +293,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeRelations = nodeRoutes.get(originatorNodeId); if (ruleNodeRelations == null) { // When unchecked, this will cause NullPointerException when rule node doesn't exist anymore diff --git a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java index e2e6f8d982..86675a79a8 100644 --- a/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java +++ b/application/src/main/java/org/thingsboard/server/config/RateLimitProcessingFilter.java @@ -15,17 +15,21 @@ */ package org.thingsboard.server.config; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.GenericFilterBean; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.service.security.model.SecurityUser; @@ -35,42 +39,40 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +@Slf4j @Component public class RateLimitProcessingFilter extends GenericFilterBean { - @Value("${server.rest.limits.tenant.enabled:false}") - private boolean perTenantLimitsEnabled; - @Value("${server.rest.limits.tenant.configuration:}") - private String perTenantLimitsConfiguration; - @Value("${server.rest.limits.customer.enabled:false}") - private boolean perCustomerLimitsEnabled; - @Value("${server.rest.limits.customer.configuration:}") - private String perCustomerLimitsConfiguration; - @Autowired private ThingsboardErrorResponseHandler errorResponseHandler; - private ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); - private ConcurrentMap perCustomerLimits = new ConcurrentHashMap<>(); + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + + private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap perCustomerLimits = new ConcurrentHashMap<>(); @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { SecurityUser user = getCurrentUser(); if (user != null && !user.isSystemAdmin()) { - if (perTenantLimitsEnabled) { - TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(user.getTenantId(), id -> new TbRateLimits(perTenantLimitsConfiguration)); - if (!rateLimits.tryConsume()) { - errorResponseHandler.handle(new TbRateLimitsException(EntityType.TENANT), (HttpServletResponse) response); - return; - } + var profile = tenantProfileCache.get(user.getTenantId()); + if (profile == null) { + log.debug("[{}] Failed to lookup tenant profile", user.getTenantId()); + errorResponseHandler.handle(new BadCredentialsException("Failed to lookup tenant profile"), (HttpServletResponse) response); + return; + } + var profileConfiguration = profile.getDefaultProfileConfiguration(); + if (!checkRateLimits(user.getTenantId(), profileConfiguration.getTenantServerRestLimitsConfiguration(), perTenantLimits, response)) { + return; } - if (perCustomerLimitsEnabled && user.isCustomerUser()) { - TbRateLimits rateLimits = perCustomerLimits.computeIfAbsent(user.getCustomerId(), id -> new TbRateLimits(perCustomerLimitsConfiguration)); - if (!rateLimits.tryConsume()) { - errorResponseHandler.handle(new TbRateLimitsException(EntityType.CUSTOMER), (HttpServletResponse) response); + if (user.isCustomerUser()) { + if (!checkRateLimits(user.getCustomerId(), profileConfiguration.getCustomerServerRestLimitsConfiguration(), perCustomerLimits, response)) { return; } } @@ -78,6 +80,25 @@ public class RateLimitProcessingFilter extends GenericFilterBean { chain.doFilter(request, response); } + private boolean checkRateLimits(I ownerId, String rateLimitConfig, Map rateLimitsMap, ServletResponse response) { + if (StringUtils.isNotEmpty(rateLimitConfig)) { + TbRateLimits rateLimits = rateLimitsMap.get(ownerId); + if (rateLimits == null || !rateLimits.getConfiguration().equals(rateLimitConfig)) { + rateLimits = new TbRateLimits(rateLimitConfig); + rateLimitsMap.put(ownerId, rateLimits); + } + + if (!rateLimits.tryConsume()) { + errorResponseHandler.handle(new TbRateLimitsException(ownerId.getEntityType()), (HttpServletResponse) response); + return false; + } + } else { + rateLimitsMap.remove(ownerId); + } + + return true; + } + protected SecurityUser getCurrentUser() { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication != null && authentication.getPrincipal() instanceof SecurityUser) { diff --git a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java index 823bbf35e3..a6540f007a 100644 --- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java @@ -24,22 +24,22 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.Order; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.DefaultAuthenticationEventPublisher; +import org.springframework.security.config.annotation.ObjectPostProcessor; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.builders.WebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; +import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.CorsFilter; -import org.thingsboard.server.dao.audit.AuditLogLevelFilter; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -63,7 +63,7 @@ import java.util.List; @EnableGlobalMethodSecurity(prePostEnabled=true) @Order(SecurityProperties.BASIC_AUTH_ORDER) @TbCoreComponent -public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter { +public class ThingsboardSecurityConfiguration { public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; public static final String JWT_TOKEN_HEADER_PARAM_V2 = "Authorization"; @@ -161,16 +161,15 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt } @Bean - @Override - public AuthenticationManager authenticationManagerBean() throws Exception { - return super.authenticationManagerBean(); - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) { + public AuthenticationManager authenticationManager(ObjectPostProcessor objectPostProcessor) throws Exception { + DefaultAuthenticationEventPublisher eventPublisher = objectPostProcessor + .postProcess(new DefaultAuthenticationEventPublisher()); + var auth = new AuthenticationManagerBuilder(objectPostProcessor); + auth.authenticationEventPublisher(eventPublisher); auth.authenticationProvider(restAuthenticationProvider); auth.authenticationProvider(jwtAuthenticationProvider); auth.authenticationProvider(refreshTokenAuthenticationProvider); + return auth.build(); } @Bean @@ -181,13 +180,20 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @Autowired private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver; - @Override - protected void configure(HttpSecurity http) throws Exception { - http.authorizeHttpRequests((authorizeHttpRequests) -> - authorizeHttpRequests - .antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**") - .permitAll() - ); + @Bean + @Order(0) + SecurityFilterChain resources(HttpSecurity http) throws Exception { + http + .requestMatchers((matchers) -> matchers.antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")) + .authorizeHttpRequests((authorize) -> authorize.anyRequest().permitAll()) + .requestCache().disable() + .securityContext().disable() + .sessionManagement().disable(); + return http.build(); + } + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.headers().cacheControl().and().frameOptions().disable() .and() .cors() @@ -229,6 +235,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt .successHandler(oauth2AuthenticationSuccessHandler) .failureHandler(oauth2AuthenticationFailureHandler); } + return http.build(); } @Bean @@ -242,5 +249,4 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt return new CorsFilter(source); } } - } diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index e5934cbf26..54cbdd9b0f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -16,16 +16,16 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.server.common.data.AdminSettings; @@ -34,14 +34,18 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.sms.config.TestSmsRequest; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; +import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsService; import org.thingsboard.server.service.update.UpdateService; -import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.*; @RestController @TbCoreComponent @@ -60,6 +64,12 @@ public class AdminController extends BaseController { @Autowired private SystemSecurityService systemSecurityService; + @Autowired + private EntitiesVersionControlService versionControlService; + + @Autowired + private TbAutoCommitSettingsService autoCommitSettingsService; + @Autowired private UpdateService updateService; @@ -96,6 +106,7 @@ public class AdminController extends BaseController { @RequestBody AdminSettings adminSettings) throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); + adminSettings.setTenantId(getTenantId()); adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); if (adminSettings.getKey().equals("mail")) { mailService.updateMailConfiguration(); @@ -180,6 +191,137 @@ public class AdminController extends BaseController { } } + @ApiOperation(value = "Get repository settings (getRepositorySettings)", + notes = "Get the repository settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/repositorySettings") + @ResponseBody + public RepositorySettings getRepositorySettings() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + RepositorySettings versionControlSettings = checkNotNull(versionControlService.getVersionControlSettings(getTenantId())); + versionControlSettings.setPassword(null); + versionControlSettings.setPrivateKey(null); + versionControlSettings.setPrivateKeyPassword(null); + return versionControlSettings; + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Check repository settings exists (repositorySettingsExists)", + notes = "Check whether the repository settings exists. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/repositorySettings/exists") + @ResponseBody + public Boolean repositorySettingsExists() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return versionControlService.getVersionControlSettings(getTenantId()) != null; + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Creates or Updates the repository settings (saveRepositorySettings)", + notes = "Creates or Updates the repository settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @PostMapping("/repositorySettings") + public DeferredResult saveRepositorySettings(@RequestBody RepositorySettings settings) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); + ListenableFuture future = versionControlService.saveVersionControlSettings(getTenantId(), settings); + return wrapFuture(Futures.transform(future, savedSettings -> { + savedSettings.setPassword(null); + savedSettings.setPrivateKey(null); + savedSettings.setPrivateKeyPassword(null); + return savedSettings; + }, MoreExecutors.directExecutor())); + } + + @ApiOperation(value = "Delete repository settings (deleteRepositorySettings)", + notes = "Deletes the repository settings." + + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/repositorySettings", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public DeferredResult deleteRepositorySettings() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); + return wrapFuture(versionControlService.deleteVersionControlSettings(getTenantId())); + } catch (Exception e) { + throw handleException(e); + } + } + + + @ApiOperation(value = "Check repository access (checkRepositoryAccess)", + notes = "Attempts to check repository access. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/repositorySettings/checkAccess", method = RequestMethod.POST) + public DeferredResult checkRepositoryAccess( + @ApiParam(value = "A JSON value representing the Repository Settings.") + @RequestBody RepositorySettings settings) throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + settings = checkNotNull(settings); + return wrapFuture(versionControlService.checkVersionControlAccess(getTenantId(), settings)); + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Get auto commit settings (getAutoCommitSettings)", + notes = "Get the auto commit settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/autoCommitSettings") + @ResponseBody + public AutoCommitSettings getAutoCommitSettings() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return checkNotNull(autoCommitSettingsService.get(getTenantId())); + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Check auto commit settings exists (autoCommitSettingsExists)", + notes = "Check whether the auto commit settings exists. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/autoCommitSettings/exists") + @ResponseBody + public Boolean autoCommitSettingsExists() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return autoCommitSettingsService.get(getTenantId()) != null; + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Creates or Updates the auto commit settings (saveAutoCommitSettings)", + notes = "Creates or Updates the auto commit settings object. " + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @PostMapping("/autoCommitSettings") + public AutoCommitSettings saveAutoCommitSettings(@RequestBody AutoCommitSettings settings) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); + return autoCommitSettingsService.save(getTenantId(), settings); + } + + @ApiOperation(value = "Delete auto commit settings (deleteAutoCommitSettings)", + notes = "Deletes the auto commit settings." + + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/autoCommitSettings", method = RequestMethod.DELETE) + @ResponseStatus(value = HttpStatus.OK) + public void deleteAutoCommitSettings() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); + autoCommitSettingsService.delete(getTenantId()); + } catch (Exception e) { + throw handleException(e); + } + } + @ApiOperation(value = "Check for new Platform Releases (checkUpdates)", notes = "Check notifications about new platform releases. " + SYSTEM_AUTHORITY_PARAGRAPH) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 939e9da8a0..d2e11086c7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -126,13 +126,15 @@ public class AlarmController extends BaseController { "\n\nPlatform also deduplicate the alarms based on the entity id of originator and alarm 'type'. " + "For example, if the user or system component create the alarm with the type 'HighTemperature' for device 'Device A' the new active alarm is created. " + "If the user tries to create 'HighTemperature' alarm for the same device again, the previous alarm will be updated (the 'end_ts' will be set to current timestamp). " + - "If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH + "If the user clears the alarm (see 'Clear Alarm(clearAlarm)'), than new alarm with the same type and same device may be created. " + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Alarm entity. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH , produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm", method = RequestMethod.POST) @ResponseBody public Alarm saveAlarm(@ApiParam(value = "A JSON value representing the alarm.") @RequestBody Alarm alarm) throws ThingsboardException { - alarm.setTenantId(getCurrentUser().getTenantId()); + alarm.setTenantId(getTenantId()); checkEntity(alarm.getId(), alarm, Resource.ALARM); return tbAlarmService.save(alarm, getCurrentUser()); } @@ -144,13 +146,9 @@ public class AlarmController extends BaseController { @ResponseBody public Boolean deleteAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { checkParameter(ALARM_ID, strAlarmId); - try { - AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); - Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); - return tbAlarmService.delete(alarm, getCurrentUser()); - } catch (Exception e) { - throw handleException(e); - } + AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); + Alarm alarm = checkAlarmId(alarmId, Operation.DELETE); + return tbAlarmService.delete(alarm, getCurrentUser()); } @ApiOperation(value = "Acknowledge Alarm (ackAlarm)", @@ -160,11 +158,11 @@ public class AlarmController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public void ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { + public void ackAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); - tbAlarmService.ack(alarm, getCurrentUser()); + tbAlarmService.ack(alarm, getCurrentUser()).get(); } @ApiOperation(value = "Clear Alarm (clearAlarm)", @@ -174,11 +172,11 @@ public class AlarmController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/{alarmId}/clear", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public void clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException { + public void clearAlarm(@ApiParam(value = ALARM_ID_PARAM_DESCRIPTION) @PathVariable(ALARM_ID) String strAlarmId) throws Exception { checkParameter(ALARM_ID, strAlarmId); AlarmId alarmId = new AlarmId(toUUID(strAlarmId)); Alarm alarm = checkAlarmId(alarmId, Operation.WRITE); - tbAlarmService.clear(alarm, getCurrentUser()); + tbAlarmService.clear(alarm, getCurrentUser()).get(); } @ApiOperation(value = "Get Alarms (getAlarms)", diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index 4718feb774..75e77d4719 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -51,9 +51,9 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.asset.AssetBulkImportService; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.service.entitiy.asset.TbAssetService; -import org.thingsboard.server.service.importing.BulkImportRequest; -import org.thingsboard.server.service.importing.BulkImportResult; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -139,15 +139,17 @@ public class AssetController extends BaseController { notes = "Creates or Updates the Asset. When creating asset, platform generates Asset Id as " + UUID_WIKI_LINK + "The newly created Asset id will be present in the response. " + "Specify existing Asset id to update the asset. " + - "Referencing non-existing Asset Id will cause 'Not Found' error." + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) + "Referencing non-existing Asset Id will cause 'Not Found' error. " + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Asset entity. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/asset", method = RequestMethod.POST) @ResponseBody - public Asset saveAsset(@ApiParam(value = "A JSON value representing the asset.") @RequestBody Asset asset) throws ThingsboardException { + public Asset saveAsset(@ApiParam(value = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception { if (TB_SERVICE_QUEUE.equals(asset.getType())) { throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); } - asset.setTenantId(getCurrentUser().getTenantId()); + asset.setTenantId(getTenantId()); checkEntity(asset.getId(), asset, Resource.ASSET); return tbAssetService.save(asset, getCurrentUser()); } @@ -157,15 +159,11 @@ public class AssetController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteAsset(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { + public void deleteAsset(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws Exception { checkParameter(ASSET_ID, strAssetId); - try { - AssetId assetId = new AssetId(toUUID(strAssetId)); - Asset asset = checkAssetId(assetId, Operation.DELETE); - tbAssetService.delete(asset, getCurrentUser()).get(); - } catch (Exception e) { - throw handleException(e); - } + AssetId assetId = new AssetId(toUUID(strAssetId)); + Asset asset = checkAssetId(assetId, Operation.DELETE); + tbAssetService.delete(asset, getCurrentUser()).get(); } @ApiOperation(value = "Assign asset to customer (assignAssetToCustomer)", diff --git a/application/src/main/java/org/thingsboard/server/controller/AutoCommitController.java b/application/src/main/java/org/thingsboard/server/controller/AutoCommitController.java new file mode 100644 index 0000000000..1a097e6817 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/AutoCommitController.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.controller; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; + +import java.util.UUID; + +public class AutoCommitController extends BaseController { + + @Autowired + private EntitiesVersionControlService vcService; + + protected ListenableFuture autoCommit(User user, EntityId entityId) throws Exception { + if (vcService != null) { + return vcService.autoCommit(user, entityId); + } else { + // We do not support auto-commit for rule engine + return Futures.immediateFailedFuture(new RuntimeException("Operation not supported!")); + } + } + + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 050745844b..7d2d2cd255 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -15,9 +15,12 @@ */ package org.thingsboard.server.controller; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -29,6 +32,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.async.AsyncRequestTimeoutException; +import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; @@ -39,7 +44,6 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; -import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; @@ -53,7 +57,6 @@ import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; @@ -88,7 +91,6 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.queue.Queue; -import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; @@ -126,10 +128,10 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService; +import org.thingsboard.server.service.entitiy.TbNotificationEntityService; import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.resource.TbResourceService; @@ -138,6 +140,7 @@ import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.state.DeviceStateService; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; @@ -276,11 +279,14 @@ public abstract class BaseController { protected EdgeRpcService edgeGrpcService; @Autowired - protected EntityActionService entityActionService; + protected TbNotificationEntityService notificationEntityService; @Autowired protected QueueService queueService; + @Autowired + protected EntitiesVersionControlService vcService; + @Value("${server.log_controller_error_stack_trace}") @Getter private boolean logControllerErrorStackTrace; @@ -341,6 +347,8 @@ public abstract class BaseController { return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS); } else if (exception instanceof MessagingException) { return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); + } else if (exception instanceof AsyncRequestTimeoutException) { + return new ThingsboardException("Request timeout", ThingsboardErrorCode.GENERAL); } else { return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.GENERAL); } @@ -882,95 +890,20 @@ public abstract class BaseController { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } - protected void logEntityAction(I entityId, E entity, CustomerId customerId, - ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { - logEntityAction(getCurrentUser(), entityId, entity, customerId, actionType, e, additionalInfo); - } - - protected void logEntityAction(User user, I entityId, E entity, CustomerId customerId, - ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { - entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); - } - - public static Exception toException(Throwable error) { return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null; } - protected String entityToStr(E entity) { - try { - return json.writeValueAsString(json.valueToTree(entity)); - } catch (JsonProcessingException e) { - log.warn("[{}] Failed to convert entity to string!", entity, e); - } - return null; - } - - protected void sendRelationNotificationMsg(TenantId tenantId, EntityRelation relation, EdgeEventActionType action) { - try { - if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && - !relation.getTo().getEntityType().equals(EntityType.EDGE)) { - sendNotificationMsgToEdgeService(tenantId, null, null, json.writeValueAsString(relation), EdgeEventType.RELATION, action); - } - } catch (Exception e) { - log.warn("Failed to push relation to core: {}", relation, e); - } - } - - protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds) { - sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null); - } - - protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, String body) { - if (edgeIds != null && !edgeIds.isEmpty()) { - for (EdgeId edgeId : edgeIds) { - sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, null, EdgeEventActionType.DELETED); - } - } - } - - protected void sendAlarmDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, Alarm alarm) { - try { - sendDeleteNotificationMsg(tenantId, entityId, edgeIds, json.writeValueAsString(alarm)); - } catch (Exception e) { - log.warn("Failed to push delete alarm msg to core: {}", alarm, e); - } - } - - protected void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) { - try { - sendNotificationMsgToEdgeService(tenantId, null, entityId, json.writeValueAsString(customerId), null, action); - } catch (Exception e) { - log.warn("Failed to push assign/unassign to/from customer to core: {}", customerId, e); - } - } - protected void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdgeService(tenantId, null, entityId, null, null, action); + sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); } protected void sendEntityAssignToEdgeNotificationMsg(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, null, action); + sendNotificationMsgToEdge(tenantId, edgeId, entityId, null, null, action); } - private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { - tbClusterService.sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, type, action); - } - - protected List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { - if (!edgesEnabled) { - return null; - } - if (EntityType.EDGE.equals(entityId.getEntityType())) { - return Collections.singletonList(new EdgeId(entityId.getId())); - } - PageDataIterableByTenantIdEntityId relatedEdgeIdsIterator = - new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE); - List result = new ArrayList<>(); - for (EdgeId edgeId : relatedEdgeIdsIterator) { - result.add(edgeId); - } - return result; + private void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { + tbClusterService.sendNotificationMsgToEdge(tenantId, edgeId, entityId, body, type, action); } protected void processDashboardIdFromAdditionalInfo(ObjectNode additionalInfo, String requiredFields) throws ThingsboardException { @@ -989,4 +922,20 @@ public abstract class BaseController { return MediaType.APPLICATION_OCTET_STREAM; } } + + protected DeferredResult wrapFuture(ListenableFuture future) { + final DeferredResult deferredResult = new DeferredResult<>(); + Futures.addCallback(future, new FutureCallback<>() { + @Override + public void onSuccess(T result) { + deferredResult.setResult(result); + } + + @Override + public void onFailure(Throwable t) { + deferredResult.setErrorResult(t); + } + }, MoreExecutors.directExecutor()); + return deferredResult; + } } diff --git a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java index a26493000a..bdf74c9055 100644 --- a/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java +++ b/application/src/main/java/org/thingsboard/server/controller/ControllerConstants.java @@ -30,7 +30,7 @@ public class ControllerConstants { protected static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " + "The result is wrapped with PageData object that allows you to iterate over result set using pagination. " + "See the 'Model' tab of the Response Class for more details. "; - protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the dashboard id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String RPC_ID_PARAM_DESCRIPTION = "A string value representing the rpc id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String ENTITY_VIEW_ID_PARAM_DESCRIPTION = "A string value representing the entity view id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; @@ -49,6 +49,7 @@ public class ControllerConstants { protected static final String RULE_NODE_ID_PARAM_DESCRIPTION = "A string value representing the rule node id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String WIDGET_BUNDLE_ID_PARAM_DESCRIPTION = "A string value representing the widget bundle id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String WIDGET_TYPE_ID_PARAM_DESCRIPTION = "A string value representing the widget type id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String VC_REQUEST_ID_PARAM_DESCRIPTION = "A string value representing the version control request id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String RESOURCE_ID_PARAM_DESCRIPTION = "A string value representing the resource id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; protected static final String SYSTEM_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'SYS_ADMIN' authority."; protected static final String SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH = "\n\nAvailable for users with 'SYS_ADMIN' or 'TENANT_ADMIN' authority."; @@ -111,6 +112,10 @@ public class ControllerConstants { protected static final String DEVICE_PROFILE_INFO_DESCRIPTION = "Device Profile Info is a lightweight object that includes main information about Device Profile excluding the heavyweight configuration object. "; protected static final String QUEUE_SERVICE_TYPE_DESCRIPTION = "Service type (implemented only for the TB-RULE-ENGINE)"; protected static final String QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES = "TB-RULE-ENGINE, TB-CORE, TB-TRANSPORT, JS-EXECUTOR"; + protected static final String QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the queue name."; + protected static final String QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, topic"; + protected static final String QUEUE_ID_PARAM_DESCRIPTION = "A string value representing the queue id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; + protected static final String QUEUE_NAME_PARAM_DESCRIPTION = "A string value representing the queue id. For example, 'Main'"; protected static final String OTA_PACKAGE_INFO_DESCRIPTION = "OTA Package Info is a lightweight object that includes main information about the OTA Package excluding the heavyweight data. "; protected static final String OTA_PACKAGE_DESCRIPTION = "OTA Package is a heavyweight object that includes main information about the OTA Package and also data. "; protected static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES = "MD5, SHA256, SHA384, SHA512, CRC32, MURMUR3_32, MURMUR3_128"; @@ -135,6 +140,10 @@ public class ControllerConstants { protected static final String EDGE_ASSIGN_ASYNC_FIRST_STEP_DESCRIPTION = "Assignment works in async way - first, notification event pushed to edge service queue on platform. "; protected static final String EDGE_ASSIGN_RECEIVE_STEP_DESCRIPTION = "(Edge will receive this instantly, if it's currently connected, or once it's going to be connected to platform). "; + protected static final String ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'substring' filter based on the entity version name."; + protected static final String VERSION_ID_PARAM_DESCRIPTION = "Version id, for example fd82625bdd7d6131cf8027b44ee967012ecaf990. Represents commit hash."; + protected static final String BRANCH_PARAM_DESCRIPTION = "The name of the working branch, for example 'master'"; + protected static final String MARKDOWN_CODE_BLOCK_START = "```json\n"; protected static final String MARKDOWN_CODE_BLOCK_END = "\n```"; protected static final String EVENT_ERROR_FILTER_OBJ = MARKDOWN_CODE_BLOCK_START + diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 4ebcde7d36..591eaa1318 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -138,11 +138,13 @@ public class CustomerController extends BaseController { notes = "Creates or Updates the Customer. When creating customer, platform generates Customer Id as " + UUID_WIKI_LINK + "The newly created Customer Id will be present in the response. " + "Specify existing Customer Id to update the Customer. " + - "Referencing non-existing Customer Id will cause 'Not Found' error." + TENANT_AUTHORITY_PARAGRAPH) + "Referencing non-existing Customer Id will cause 'Not Found' error." + + "Remove 'id', 'tenantId' from the request body example (below) to create new Customer entity. " + + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer", method = RequestMethod.POST) @ResponseBody - public Customer saveCustomer(@ApiParam(value = "A JSON value representing the customer.") @RequestBody Customer customer) throws ThingsboardException { + public Customer saveCustomer(@ApiParam(value = "A JSON value representing the customer.") @RequestBody Customer customer) throws Exception { customer.setTenantId(getTenantId()); checkEntity(customer.getId(), customer, Resource.CUSTOMER); return tbCustomerService.save(customer, getCurrentUser()); @@ -160,11 +162,7 @@ public class CustomerController extends BaseController { checkParameter(CUSTOMER_ID, strCustomerId); CustomerId customerId = new CustomerId(toUUID(strCustomerId)); Customer customer = checkCustomerId(customerId, Operation.DELETE); - try { - tbCustomerService.delete(customer, getCurrentUser()); - } catch (Exception e) { - throw handleException(e); - } + tbCustomerService.delete(customer, getCurrentUser()); } @ApiOperation(value = "Get Tenant Customers (getCustomers)", diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 47acdc64a4..9ceeb0d14c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -172,6 +172,7 @@ public class DashboardController extends BaseController { "The newly created Dashboard id will be present in the response. " + "Specify existing Dashboard id to update the dashboard. " + "Referencing non-existing dashboard Id will cause 'Not Found' error. " + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Dashboard entity. " + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE, consumes = MediaType.APPLICATION_JSON_VALUE) @@ -180,7 +181,7 @@ public class DashboardController extends BaseController { @ResponseBody public Dashboard saveDashboard( @ApiParam(value = "A JSON value representing the dashboard.") - @RequestBody Dashboard dashboard) throws ThingsboardException { + @RequestBody Dashboard dashboard) throws Exception { dashboard.setTenantId(getTenantId()); checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD); return tbDashboardService.save(dashboard, getCurrentUser()); @@ -219,8 +220,8 @@ public class DashboardController extends BaseController { Customer customer = checkCustomerId(customerId, Operation.READ); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); - return tbDashboardService.assignDashboardToCustomer(dashboardId, customer, getCurrentUser()); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); + return tbDashboardService.assignDashboardToCustomer(dashboard, customer, getCurrentUser()); } @ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)", @@ -261,7 +262,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); - Set customerIds = customerIdFromStr(strCustomerIds, dashboard); + Set customerIds = customerIdFromStr(strCustomerIds); return tbDashboardService.updateDashboardCustomers(dashboard, customerIds, getCurrentUser()); } @@ -281,7 +282,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); - Set customerIds = customerIdFromStr(strCustomerIds, dashboard); + Set customerIds = customerIdFromStr(strCustomerIds); return tbDashboardService.addDashboardCustomers(dashboard, customerIds, getCurrentUser()); } @@ -301,7 +302,7 @@ public class DashboardController extends BaseController { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_CUSTOMER); - Set customerIds = customerIdFromStr(strCustomerIds, dashboard); + Set customerIds = customerIdFromStr(strCustomerIds); return tbDashboardService.removeDashboardCustomers(dashboard, customerIds, getCurrentUser()); } @@ -321,8 +322,8 @@ public class DashboardController extends BaseController { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { checkParameter(DASHBOARD_ID, strDashboardId); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); - checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); - return tbDashboardService.assignDashboardToPublicCustomer(dashboardId, getCurrentUser()); + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); + return tbDashboardService.assignDashboardToPublicCustomer(dashboard, getCurrentUser()); } @ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)", @@ -631,7 +632,7 @@ public class DashboardController extends BaseController { DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); checkDashboardId(dashboardId, Operation.READ); - return tbDashboardService.asignDashboardToEdge(dashboardId, edge, getCurrentUser()); + return tbDashboardService.asignDashboardToEdge(getTenantId(), dashboardId, edge, getCurrentUser()); } @ApiOperation(value = "Unassign dashboard from edge (unassignDashboardFromEdge)", @@ -704,14 +705,11 @@ public class DashboardController extends BaseController { } } - private Set customerIdFromStr(String [] strCustomerIds, Dashboard dashboard) { + private Set customerIdFromStr(String[] strCustomerIds) { Set customerIds = new HashSet<>(); if (strCustomerIds != null) { for (String strCustomerId : strCustomerIds) { - CustomerId customerId = new CustomerId(UUID.fromString(strCustomerId)); - if (dashboard.isAssignedToCustomer(customerId)) { - customerIds.add(customerId); - } + customerIds.add(new CustomerId(UUID.fromString(strCustomerId))); } } return customerIds; diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 793ea64336..ae1558381f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -66,9 +66,9 @@ import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.device.DeviceBulkImportService; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.service.entitiy.device.TbDeviceService; -import org.thingsboard.server.service.importing.BulkImportRequest; -import org.thingsboard.server.service.importing.BulkImportResult; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -158,14 +158,15 @@ public class DeviceController extends BaseController { "The newly created device id will be present in the response. " + "Specify existing Device id to update the device. " + "Referencing non-existing device Id will cause 'Not Found' error." + - "\n\nDevice name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the device names and non-unique 'label' field for user-friendly visualization purposes." - + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) + "\n\nDevice name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the device names and non-unique 'label' field for user-friendly visualization purposes." + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity. " + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device", method = RequestMethod.POST) @ResponseBody public Device saveDevice(@ApiParam(value = "A JSON value representing the device.") @RequestBody Device device, @ApiParam(value = "Optional value of the device credentials to be used during device creation. " + - "If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { + "If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws Exception { device.setTenantId(getCurrentUser().getTenantId()); Device oldDevice = null; if (device.getId() != null) { @@ -173,7 +174,7 @@ public class DeviceController extends BaseController { } else { checkEntity(null, device, Resource.DEVICE); } - return tbDeviceService.save(getTenantId(), device, oldDevice, accessToken, getCurrentUser()); + return tbDeviceService.save(device, oldDevice, accessToken, getCurrentUser()); } @ApiOperation(value = "Create Device (saveDevice) with credentials ", @@ -181,6 +182,7 @@ public class DeviceController extends BaseController { "Requires to provide the Device Credentials object as well. Useful to create device and credentials in one request. " + "You may find the example of LwM2M device and RPK credentials below: \n\n" + DEVICE_WITH_DEVICE_CREDENTIALS_PARAM_DESCRIPTION_MARKDOWN + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device entity. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device-with-credentials", method = RequestMethod.POST) @@ -191,25 +193,20 @@ public class DeviceController extends BaseController { DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials()); device.setTenantId(getCurrentUser().getTenantId()); checkEntity(device.getId(), device, Resource.DEVICE); - return tbDeviceService.saveDeviceWithCredentials(getTenantId(), device, credentials, getCurrentUser()); + return tbDeviceService.saveDeviceWithCredentials(device, credentials, getCurrentUser()); } - @ApiOperation(value = "Delete device (deleteDevice)", notes = "Deletes the device, it's credentials and all the relations (from and to the device). Referencing non-existing device Id will cause an error." + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteDevice(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) - @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { + @PathVariable(DEVICE_ID) String strDeviceId) throws Exception { checkParameter(DEVICE_ID, strDeviceId); DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); Device device = checkDeviceId(deviceId, Operation.DELETE); - try { - tbDeviceService.delete(device, getCurrentUser()).get(); - } catch (Exception e) { - throw handleException(e); - } + tbDeviceService.delete(device, getCurrentUser()).get(); } @ApiOperation(value = "Assign device to customer (assignDeviceToCustomer)", @@ -238,19 +235,15 @@ public class DeviceController extends BaseController { public Device unassignDeviceFromCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); - try { - DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); - Device device = checkDeviceId(deviceId, Operation.UNASSIGN_FROM_CUSTOMER); - if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - throw new IncorrectParameterException("Device isn't assigned to any customer!"); - } + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.UNASSIGN_FROM_CUSTOMER); + if (device.getCustomerId() == null || device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { + throw new IncorrectParameterException("Device isn't assigned to any customer!"); + } - Customer customer = checkCustomerId(device.getCustomerId(), Operation.READ); + Customer customer = checkCustomerId(device.getCustomerId(), Operation.READ); - return tbDeviceService.unassignDeviceFromCustomer(device, customer, getCurrentUser()); - } catch (Exception e) { - throw handleException(e); - } + return tbDeviceService.unassignDeviceFromCustomer(device, customer, getCurrentUser()); } @ApiOperation(value = "Make device publicly available (assignDeviceToPublicCustomer)", @@ -566,7 +559,7 @@ public class DeviceController extends BaseController { ListenableFuture future = tbDeviceService.claimDevice(tenantId, device, customerId, secretKey, user); - Futures.addCallback(future, new FutureCallback() { + Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(@Nullable ClaimResult result) { HttpStatus status; @@ -600,32 +593,28 @@ public class DeviceController extends BaseController { public DeferredResult reClaimDevice(@ApiParam(value = "Unique name of the device which is going to be reclaimed") @PathVariable(DEVICE_NAME) String deviceName) throws ThingsboardException { checkParameter(DEVICE_NAME, deviceName); - try { - final DeferredResult deferredResult = new DeferredResult<>(); + final DeferredResult deferredResult = new DeferredResult<>(); - SecurityUser user = getCurrentUser(); - TenantId tenantId = user.getTenantId(); + SecurityUser user = getCurrentUser(); + TenantId tenantId = user.getTenantId(); - Device device = checkNotNull(deviceService.findDeviceByTenantIdAndName(tenantId, deviceName)); - accessControlService.checkPermission(user, Resource.DEVICE, Operation.CLAIM_DEVICES, - device.getId(), device); + Device device = checkNotNull(deviceService.findDeviceByTenantIdAndName(tenantId, deviceName)); + accessControlService.checkPermission(user, Resource.DEVICE, Operation.CLAIM_DEVICES, + device.getId(), device); - ListenableFuture result = tbDeviceService.reclaimDevice(tenantId, device, user); - Futures.addCallback(result, new FutureCallback<>() { - @Override - public void onSuccess(ReclaimResult reclaimResult) { - deferredResult.setResult(new ResponseEntity(HttpStatus.OK)); - } + ListenableFuture result = tbDeviceService.reclaimDevice(tenantId, device, user); + Futures.addCallback(result, new FutureCallback<>() { + @Override + public void onSuccess(ReclaimResult reclaimResult) { + deferredResult.setResult(new ResponseEntity(HttpStatus.OK)); + } - @Override - public void onFailure(Throwable t) { - deferredResult.setErrorResult(t); - } - }, MoreExecutors.directExecutor()); - return deferredResult; - } catch (Exception e) { - throw handleException(e); - } + @Override + public void onFailure(Throwable t) { + deferredResult.setErrorResult(t); + } + }, MoreExecutors.directExecutor()); + return deferredResult; } private String getSecretKey(ClaimRequest claimRequest) { diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java index c9330c0a77..e45a195a2d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -39,7 +39,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entitiy.deviceProfile.TbDeviceProfileService; +import org.thingsboard.server.service.entitiy.device.profile.TbDeviceProfileService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -71,7 +71,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI @Slf4j public class DeviceProfileController extends BaseController { - private final TbDeviceProfileService tbDeviceProfileService; + private final TbDeviceProfileService tbDeviceProfileService; @Autowired private TimeseriesService timeseriesService; @@ -108,7 +108,7 @@ public class DeviceProfileController extends BaseController { checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId); try { DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); - return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId)); + return new DeviceProfileInfo(checkDeviceProfileId(deviceProfileId, Operation.READ)); } catch (Exception e) { throw handleException(e); } @@ -191,6 +191,7 @@ public class DeviceProfileController extends BaseController { "Specify existing device profile id to update the device profile. " + "Referencing non-existing device profile Id will cause 'Not Found' error. " + NEW_LINE + "Device profile name is unique in the scope of tenant. Only one 'default' device profile may exist in scope of tenant." + DEVICE_PROFILE_DATA + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Device Profile entity. " + TENANT_AUTHORITY_PARAGRAPH, produces = "application/json", consumes = "application/json") @@ -199,7 +200,7 @@ public class DeviceProfileController extends BaseController { @ResponseBody public DeviceProfile saveDeviceProfile( @ApiParam(value = "A JSON value representing the device profile.") - @RequestBody DeviceProfile deviceProfile) throws ThingsboardException { + @RequestBody DeviceProfile deviceProfile) throws Exception { deviceProfile.setTenantId(getTenantId()); checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); return tbDeviceProfileService.save(deviceProfile, getCurrentUser()); @@ -219,7 +220,7 @@ public class DeviceProfileController extends BaseController { DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId)); DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE); tbDeviceProfileService.delete(deviceProfile, getCurrentUser()); - } + } @ApiOperation(value = "Make Device Profile Default (setDefaultDeviceProfile)", notes = "Marks device profile as default within a tenant scope." + TENANT_AUTHORITY_PARAGRAPH, diff --git a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java index 5d9737db23..59c9eb39c1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EdgeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EdgeController.java @@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.edge.Edge; @@ -52,8 +53,8 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.EdgeBulkImportService; import org.thingsboard.server.service.entitiy.edge.TbEdgeService; -import org.thingsboard.server.service.importing.BulkImportRequest; -import org.thingsboard.server.service.importing.BulkImportResult; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -140,15 +141,16 @@ public class EdgeController extends BaseController { "The newly created edge id will be present in the response. " + "Specify existing Edge id to update the edge. " + "Referencing non-existing Edge Id will cause 'Not Found' error." + - "\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." - + TENANT_AUTHORITY_PARAGRAPH, + "\n\nEdge name is unique in the scope of tenant. Use unique identifiers like MAC or IMEI for the edge names and non-unique 'label' field for user-friendly visualization purposes." + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Edge entity. " + + TENANT_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/edge", method = RequestMethod.POST) @ResponseBody public Edge saveEdge(@ApiParam(value = "A JSON value representing the edge.", required = true) - @RequestBody Edge edge) throws ThingsboardException { - TenantId tenantId = getCurrentUser().getTenantId(); + @RequestBody Edge edge) throws Exception { + TenantId tenantId = getTenantId(); edge.setTenantId(tenantId); boolean created = edge.getId() == null; @@ -351,7 +353,7 @@ public class EdgeController extends BaseController { public Edge setEdgeRootRuleChain(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION, required = true) - @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException { + @PathVariable("ruleChainId") String strRuleChainId) throws Exception { checkParameter(EDGE_ID, strEdgeId); checkParameter("ruleChainId", strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); @@ -557,7 +559,7 @@ public class EdgeController extends BaseController { edgeId = checkNotNull(edgeId); SecurityUser user = getCurrentUser(); TenantId tenantId = user.getTenantId(); - return edgeService.findMissingToRelatedRuleChains(tenantId, edgeId); + return edgeService.findMissingToRelatedRuleChains(tenantId, edgeId, TbRuleChainInputNode.class.getName()); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java new file mode 100644 index 0000000000..f2bfbc9478 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java @@ -0,0 +1,519 @@ +/** + * 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 com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.EntityDataDiff; +import org.thingsboard.server.common.data.sync.vc.EntityDataInfo; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; +import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.thingsboard.server.controller.ControllerConstants.BRANCH_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END; +import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START; +import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.VC_REQUEST_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.VERSION_ID_PARAM_DESCRIPTION; + +@RestController +@TbCoreComponent +@RequestMapping("/api/entities/vc") +@PreAuthorize("hasAuthority('TENANT_ADMIN')") +@RequiredArgsConstructor +public class EntitiesVersionControlController extends BaseController { + + private final EntitiesVersionControlService versionControlService; + + + @ApiOperation(value = "Save entities version (saveEntitiesVersion)", notes = "" + + "Creates a new version of entities (or a single entity) by request.\n" + + "Supported entity types: CUSTOMER, ASSET, RULE_CHAIN, DASHBOARD, DEVICE_PROFILE, DEVICE, ENTITY_VIEW, WIDGETS_BUNDLE." + NEW_LINE + + "There are two available types of request: `SINGLE_ENTITY` and `COMPLEX`. " + + "Each of them contains version name (`versionName`) and name of a branch (`branch`) to create version (commit) in. " + + "If specified branch does not exists in a remote repo, then new empty branch will be created. " + + "Request of the `SINGLE_ENTITY` type has id of an entity (`entityId`) and additional configuration (`config`) " + + "which has following options: \n" + + "- `saveRelations` - whether to add inbound and outbound relations of type COMMON to created entity version;\n" + + "- `saveAttributes` - to save attributes of server scope (and also shared scope for devices);\n" + + "- `saveCredentials` - when saving a version of a device, to add its credentials to the version." + NEW_LINE + + "An example of a `SINGLE_ENTITY` version create request:\n" + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"type\": \"SINGLE_ENTITY\",\n" + + "\n" + + " \"versionName\": \"Version 1.0\",\n" + + " \"branch\": \"dev\",\n" + + "\n" + + " \"entityId\": {\n" + + " \"entityType\": \"DEVICE\",\n" + + " \"id\": \"b79448e0-d4f4-11ec-847b-0f432358ab48\"\n" + + " },\n" + + " \"config\": {\n" + + " \"saveRelations\": true,\n" + + " \"saveAttributes\": true,\n" + + " \"saveCredentials\": false\n" + + " }\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + NEW_LINE + + "Second request type (`COMPLEX`), additionally to `branch` and `versionName`, contains following properties:\n" + + "- `entityTypes` - a structure with entity types to export and configuration for each entity type; " + + " this configuration has all the options available for `SINGLE_ENTITY` and additionally has these ones: \n" + + " - `allEntities` and `entityIds` - if you want to save the version of all entities of the entity type " + + " then set `allEntities` param to true, otherwise set it to false and specify the list of specific entities (`entityIds`);\n" + + " - `syncStrategy` - synchronization strategy to use for this entity type: when set to `OVERWRITE` " + + " then the list of remote entities of this type will be overwritten by newly added entities. If set to " + + " `MERGE` - existing remote entities of this entity type will not be removed, new entities will just " + + " be added on top (or existing remote entities will be updated).\n" + + "- `syncStrategy` - default synchronization strategy to use when it is not specified for an entity type." + NEW_LINE + + "Example for this type of request:\n" + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"type\": \"COMPLEX\",\n" + + "\n" + + " \"versionName\": \"Devices and profiles: release 2\",\n" + + " \"branch\": \"master\",\n" + + "\n" + + " \"syncStrategy\": \"OVERWRITE\",\n" + + " \"entityTypes\": {\n" + + " \"DEVICE\": {\n" + + " \"syncStrategy\": null,\n" + + " \"allEntities\": true,\n" + + " \"saveRelations\": true,\n" + + " \"saveAttributes\": true,\n" + + " \"saveCredentials\": true\n" + + " },\n" + + " \"DEVICE_PROFILE\": {\n" + + " \"syncStrategy\": \"MERGE\",\n" + + " \"allEntities\": false,\n" + + " \"entityIds\": [\n" + + " \"b79448e0-d4f4-11ec-847b-0f432358ab48\"\n" + + " ],\n" + + " \"saveRelations\": true\n" + + " }\n" + + " }\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + NEW_LINE + + "Response wil contain generated request UUID, that can be then used to retrieve " + + "status of operation via `getVersionCreateRequestStatus`.\n" + + TENANT_AUTHORITY_PARAGRAPH) + @PostMapping("/version") + public DeferredResult saveEntitiesVersion(@RequestBody VersionCreateRequest request) throws Exception { + SecurityUser user = getCurrentUser(); + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); + return wrapFuture(versionControlService.saveEntitiesVersion(user, request)); + } + + @ApiOperation(value = "Get version create request status (getVersionCreateRequestStatus)", notes = "" + + "Returns the status of previously made version create request. " + NEW_LINE + + "This status contains following properties:\n" + + "- `done` - whether request processing is finished;\n" + + "- `version` - created version info: timestamp, version id (commit hash), commit name and commit author;\n" + + "- `added` - count of items that were created in the remote repo;\n" + + "- `modified` - modified items count;\n" + + "- `removed` - removed items count;\n" + + "- `error` - error message, if an error occurred while handling the request." + NEW_LINE + + "An example of successful status:\n" + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"done\": true,\n" + + " \"added\": 10,\n" + + " \"modified\": 2,\n" + + " \"removed\": 5,\n" + + " \"version\": {\n" + + " \"timestamp\": 1655198528000,\n" + + " \"id\":\"8a834dd389ed80e0759ba8ee338b3f1fd160a114\",\n" + + " \"name\": \"My devices v2.0\",\n" + + " \"author\": \"John Doe\"\n" + + " },\n" + + " \"error\": null\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping(value = "/version/{requestId}/status") + public VersionCreationResult getVersionCreateRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) + @PathVariable UUID requestId) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); + return versionControlService.getVersionCreateStatus(getCurrentUser(), requestId); + } + + @ApiOperation(value = "List entity versions (listEntityVersions)", notes = "" + + "Returns list of versions for a specific entity in a concrete branch. \n" + + "You need to specify external id of an entity to list versions for. This is `externalId` property of an entity, " + + "or otherwise if not set - simply id of this entity. \n" + + "If specified branch does not exist - empty page data will be returned. " + NEW_LINE + + "Each version info item has timestamp, id, name and author. Version id can then be used to restore the version. " + + PAGE_DATA_PARAMETERS + NEW_LINE + + "Response example: \n" + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"data\": [\n" + + " {\n" + + " \"timestamp\": 1655198593000,\n" + + " \"id\": \"fd82625bdd7d6131cf8027b44ee967012ecaf990\",\n" + + " \"name\": \"Devices and assets - v2.0\",\n" + + " \"author\": \"John Doe \"\n" + + " },\n" + + " {\n" + + " \"timestamp\": 1655198528000,\n" + + " \"id\": \"682adcffa9c8a2f863af6f00c4850323acbd4219\",\n" + + " \"name\": \"Update my device\",\n" + + " \"author\": \"John Doe \"\n" + + " },\n" + + " {\n" + + " \"timestamp\": 1655198280000,\n" + + " \"id\": \"d2a6087c2b30e18cc55e7cdda345a8d0dfb959a4\",\n" + + " \"name\": \"Devices and assets - v1.0\",\n" + + " \"author\": \"John Doe \"\n" + + " }\n" + + " ],\n" + + " \"totalPages\": 1,\n" + + " \"totalElements\": 3,\n" + + " \"hasNext\": false\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping(value = "/version/{entityType}/{externalEntityUuid}", params = {"branch", "pageSize", "page"}) + public DeferredResult> listEntityVersions(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @PathVariable EntityType entityType, + @ApiParam(value = "A string value representing external entity id. This is `externalId` property of an entity, or otherwise if not set - simply id of this entity.") + @PathVariable UUID externalEntityUuid, + @ApiParam(value = BRANCH_PARAM_DESCRIPTION) + @RequestParam String branch, + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortOrder) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + EntityId externalEntityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return wrapFuture(versionControlService.listEntityVersions(getTenantId(), branch, externalEntityId, pageLink)); + } + + @ApiOperation(value = "List entity type versions (listEntityTypeVersions)", notes = "" + + "Returns list of versions of an entity type in a branch. This is a collected list of versions that were created " + + "for entities of this type in a remote branch. \n" + + "If specified branch does not exist - empty page data will be returned. " + + "The response structure is the same as for `listEntityVersions` API method." + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping(value = "/version/{entityType}", params = {"branch", "pageSize", "page"}) + public DeferredResult> listEntityTypeVersions(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @PathVariable EntityType entityType, + @ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true) + @RequestParam String branch, + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortOrder) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return wrapFuture(versionControlService.listEntityTypeVersions(getTenantId(), branch, entityType, pageLink)); + } + + @ApiOperation(value = "List all versions (listVersions)", notes = "" + + "Lists all available versions in a branch for all entity types. \n" + + "If specified branch does not exist - empty page data will be returned. " + + "The response format is the same as for `listEntityVersions` API method." + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping(value = "/version", params = {"branch", "pageSize", "page"}) + public DeferredResult> listVersions(@ApiParam(value = BRANCH_PARAM_DESCRIPTION, required = true) + @RequestParam String branch, + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) + @RequestParam int page, + @ApiParam(value = ENTITY_VERSION_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = "timestamp") + @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) + @RequestParam(required = false) String sortOrder) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return wrapFuture(versionControlService.listVersions(getTenantId(), branch, pageLink)); + } + + + @ApiOperation(value = "List entities at version (listEntitiesAtVersion)", notes = "" + + "Returns a list of remote entities of a specific entity type that are available at a concrete version. \n" + + "Each entity item in the result has `externalId` property. " + + "Entities order will be the same as in the repository." + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping(value = "/entity/{entityType}/{versionId}") + public DeferredResult> listEntitiesAtVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @PathVariable EntityType entityType, + @ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + @PathVariable String versionId) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), versionId, entityType)); + } + + @ApiOperation(value = "List all entities at version (listAllEntitiesAtVersion)", notes = "" + + "Returns a list of all remote entities available in a specific version. " + + "Response type is the same as for listAllEntitiesAtVersion API method. \n" + + "Returned entities order will be the same as in the repository." + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping(value = "/entity/{versionId}") + public DeferredResult> listAllEntitiesAtVersion(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + @PathVariable String versionId) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), versionId)); + } + + @ApiOperation(value = "Get entity data info (getEntityDataInfo)", notes = "" + + "Retrieves short info about the remote entity by external id at a concrete version. \n" + + "Returned entity data info contains following properties: " + + "`hasRelations` (whether stored entity data contains relations), `hasAttributes` (contains attributes) and " + + "`hasCredentials` (whether stored device data has credentials)." + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping("/info/{versionId}/{entityType}/{externalEntityUuid}") + public DeferredResult getEntityDataInfo(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + @PathVariable String versionId, + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @PathVariable EntityType entityType, + @ApiParam(value = "A string value representing external entity id", required = true) + @PathVariable UUID externalEntityUuid) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, externalEntityUuid); + return wrapFuture(versionControlService.getEntityDataInfo(getCurrentUser(), entityId, versionId)); + } + + @ApiOperation(value = "Compare entity data to version (compareEntityDataToVersion)", notes = "" + + "Returns an object with current entity data and the one at a specific version. " + + "Entity data structure is the same as stored in a repository. " + + TENANT_AUTHORITY_PARAGRAPH) + @GetMapping(value = "/diff/{entityType}/{internalEntityUuid}", params = {"versionId"}) + public DeferredResult compareEntityDataToVersion(@ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) + @PathVariable EntityType entityType, + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) + @PathVariable UUID internalEntityUuid, + @ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true) + @RequestParam String versionId) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + EntityId entityId = EntityIdFactory.getByTypeAndUuid(entityType, internalEntityUuid); + return wrapFuture(versionControlService.compareEntityDataToVersion(getCurrentUser(), entityId, versionId)); + } + + @ApiOperation(value = "Load entities version (loadEntitiesVersion)", notes = "" + + "Loads specific version of remote entities (or single entity) by request. " + + "Supported entity types: CUSTOMER, ASSET, RULE_CHAIN, DASHBOARD, DEVICE_PROFILE, DEVICE, ENTITY_VIEW, WIDGETS_BUNDLE." + NEW_LINE + + "There are multiple types of request. Each of them requires branch name (`branch`) and version id (`versionId`). " + + "Request of type `SINGLE_ENTITY` is needed to restore a concrete version of a specific entity. It contains " + + "id of a remote entity (`externalEntityId`) and additional configuration (`config`):\n" + + "- `loadRelations` - to update relations list (in case `saveRelations` option was enabled during version creation);\n" + + "- `loadAttributes` - to load entity attributes (if `saveAttributes` config option was enabled);\n" + + "- `loadCredentials` - to update device credentials (if `saveCredentials` option was enabled during version creation)." + NEW_LINE + + "An example of such request:\n" + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"type\": \"SINGLE_ENTITY\",\n" + + " \n" + + " \"branch\": \"dev\",\n" + + " \"versionId\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + + " \n" + + " \"externalEntityId\": {\n" + + " \"entityType\": \"DEVICE\",\n" + + " \"id\": \"b7944123-d4f4-11ec-847b-0f432358ab48\"\n" + + " },\n" + + " \"config\": {\n" + + " \"loadRelations\": false,\n" + + " \"loadAttributes\": true,\n" + + " \"loadCredentials\": true\n" + + " }\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + NEW_LINE + + "Another request type (`ENTITY_TYPE`) is needed to load specific version of the whole entity types. " + + "It contains a structure with entity types to load and configs for each entity type (`entityTypes`). " + + "For each specified entity type, the method will load all remote entities of this type that are present " + + "at the version. A config for each entity type contains the same options as in `SINGLE_ENTITY` request type, and " + + "additionally contains following options:\n" + + "- `removeOtherEntities` - to remove local entities that are not present on the remote - basically to " + + " overwrite local entity type with the remote one;\n" + + "- `findExistingEntityByName` - when you are loading some remote entities that are not yet present at this tenant, " + + " try to find existing entity by name and update it rather than create new." + NEW_LINE + + "Here is an example of the request to completely restore version of the whole device entity type:\n" + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"type\": \"ENTITY_TYPE\",\n" + + "\n" + + " \"branch\": \"dev\",\n" + + " \"versionId\": \"b3c28d722d328324c7c15b0b30047b0c40011cf7\",\n" + + "\n" + + " \"entityTypes\": {\n" + + " \"DEVICE\": {\n" + + " \"removeOtherEntities\": true,\n" + + " \"findExistingEntityByName\": false,\n" + + " \"loadRelations\": true,\n" + + " \"loadAttributes\": true,\n" + + " \"loadCredentials\": true\n" + + " }\n" + + " }\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + NEW_LINE + + "The response will contain generated request UUID that is to be used to check the status of operation " + + "via `getVersionLoadRequestStatus`." + + TENANT_AUTHORITY_PARAGRAPH) + @PostMapping("/entity") + public UUID loadEntitiesVersion(@RequestBody VersionLoadRequest request) throws Exception { + SecurityUser user = getCurrentUser(); + accessControlService.checkPermission(user, Resource.VERSION_CONTROL, Operation.WRITE); + return versionControlService.loadEntitiesVersion(user, request); + } + + @ApiOperation(value = "Get version load request status (getVersionLoadRequestStatus)", notes = "" + + "Returns the status of previously made version load request. " + + "The structure contains following parameters:\n" + + "- `done` - if the request was successfully processed;\n" + + "- `result` - a list of load results for each entity type:\n" + + " - `created` - created entities count;\n" + + " - `updated` - updated entities count;\n" + + " - `deleted` - removed entities count.\n" + + "- `error` - if an error occurred during processing, error info:\n" + + " - `type` - error type;\n" + + " - `source` - an external id of remote entity;\n" + + " - `target` - if failed to find referenced entity by external id - this external id;\n" + + " - `message` - error message." + NEW_LINE + + "An example of successfully processed request status:\n" + + MARKDOWN_CODE_BLOCK_START + + "{\n" + + " \"done\": true,\n" + + " \"result\": [\n" + + " {\n" + + " \"entityType\": \"DEVICE\",\n" + + " \"created\": 10,\n" + + " \"updated\": 5,\n" + + " \"deleted\": 5\n" + + " },\n" + + " {\n" + + " \"entityType\": \"ASSET\",\n" + + " \"created\": 4,\n" + + " \"updated\": 0,\n" + + " \"deleted\": 8\n" + + " }\n" + + " ]\n" + + "}" + + MARKDOWN_CODE_BLOCK_END + + TENANT_AUTHORITY_PARAGRAPH + ) + @GetMapping(value = "/entity/{requestId}/status") + public VersionLoadResult getVersionLoadRequestStatus(@ApiParam(value = VC_REQUEST_ID_PARAM_DESCRIPTION, required = true) + @PathVariable UUID requestId) throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.WRITE); + return versionControlService.getVersionLoadStatus(getCurrentUser(), requestId); + } + + + @ApiOperation(value = "List branches (listBranches)", notes = "" + + "Lists branches available in the remote repository. \n\n" + + "Response example: \n" + + MARKDOWN_CODE_BLOCK_START + + "[\n" + + " {\n" + + " \"name\": \"master\",\n" + + " \"default\": true\n" + + " },\n" + + " {\n" + + " \"name\": \"dev\",\n" + + " \"default\": false\n" + + " },\n" + + " {\n" + + " \"name\": \"dev-2\",\n" + + " \"default\": false\n" + + " }\n" + + "]" + + MARKDOWN_CODE_BLOCK_END) + @GetMapping("/branches") + public DeferredResult> listBranches() throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + final TenantId tenantId = getTenantId(); + ListenableFuture> branches = versionControlService.listBranches(tenantId); + return wrapFuture(Futures.transform(branches, remoteBranches -> { + List infos = new ArrayList<>(); + BranchInfo defaultBranch; + String defaultBranchName = versionControlService.getVersionControlSettings(tenantId).getDefaultBranch(); + if (StringUtils.isNotEmpty(defaultBranchName)) { + defaultBranch = new BranchInfo(defaultBranchName, true); + } else { + defaultBranch = remoteBranches.stream().filter(BranchInfo::isDefault).findFirst().orElse(null); + } + if (defaultBranch != null) { + infos.add(defaultBranch); + } + infos.addAll(remoteBranches.stream().filter(b -> !b.equals(defaultBranch)) + .map(b -> new BranchInfo(b.getName(), false)).collect(Collectors.toList())); + return infos; + }, MoreExecutors.directExecutor())); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java index 0a873aaa18..efc4237b3f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java @@ -36,7 +36,8 @@ import org.thingsboard.server.common.data.relation.EntityRelationInfo; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entitiy.entityRelation.TbEntityRelationService; +import org.thingsboard.server.service.entitiy.entity.relation.TbEntityRelationService; +import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import java.util.List; @@ -54,7 +55,7 @@ import static org.thingsboard.server.controller.ControllerConstants.RELATION_TYP @RequiredArgsConstructor public class EntityRelationController extends BaseController { - private final TbEntityRelationService tbEntityRelationService; + private final TbEntityRelationService tbEntityRelationService; public static final String TO_TYPE = "toType"; public static final String FROM_ID = "fromId"; @@ -80,13 +81,13 @@ public class EntityRelationController extends BaseController { public void saveRelation(@ApiParam(value = "A JSON value representing the relation.", required = true) @RequestBody EntityRelation relation) throws ThingsboardException { checkNotNull(relation); - checkEntityId(relation.getFrom(), Operation.WRITE); - checkEntityId(relation.getTo(), Operation.WRITE); + checkCanCreateRelation(relation.getFrom()); + checkCanCreateRelation(relation.getTo()); if (relation.getTypeGroup() == null) { relation.setTypeGroup(RelationTypeGroup.COMMON); } - tbEntityRelationService.save(getTenantId(), getCurrentUser().getCustomerId(), relation, getCurrentUser()); + tbEntityRelationService.save(getTenantId(), getCurrentUser().getCustomerId(), relation, getCurrentUser()); } @ApiOperation(value = "Delete Relation (deleteRelation)", @@ -107,11 +108,11 @@ public class EntityRelationController extends BaseController { checkParameter(TO_TYPE, strToType); EntityId fromId = EntityIdFactory.getByTypeAndId(strFromType, strFromId); EntityId toId = EntityIdFactory.getByTypeAndId(strToType, strToId); - checkEntityId(fromId, Operation.WRITE); - checkEntityId(toId, Operation.WRITE); + checkCanCreateRelation(fromId); + checkCanCreateRelation(toId); + RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON); EntityRelation relation = new EntityRelation(fromId, toId, strRelationType, relationTypeGroup); - tbEntityRelationService.delete(getTenantId(), getCurrentUser().getCustomerId(), relation, getCurrentUser()); } @@ -127,7 +128,7 @@ public class EntityRelationController extends BaseController { checkParameter("entityType", strType); EntityId entityId = EntityIdFactory.getByTypeAndId(strType, strId); checkEntityId(entityId, Operation.WRITE); - tbEntityRelationService.deleteRelations (getTenantId(), getCurrentUser().getCustomerId(), entityId, getCurrentUser()); + tbEntityRelationService.deleteRelations(getTenantId(), getCurrentUser().getCustomerId(), entityId, getCurrentUser()); } @ApiOperation(value = "Get Relation (getRelation)", @@ -341,6 +342,14 @@ public class EntityRelationController extends BaseController { } } + private void checkCanCreateRelation(EntityId entityId) throws ThingsboardException { + SecurityUser currentUser = getCurrentUser(); + var isTenantAdminAndRelateToSelf = currentUser.isTenantAdmin() && currentUser.getTenantId().equals(entityId); + if (!isTenantAdminAndRelateToSelf) { + checkEntityId(entityId, Operation.WRITE); + } + } + private List filterRelationsByReadPermission(List relationsByQuery) { return relationsByQuery.stream().filter(relationByQuery -> { try { diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java index a7089969c3..0f299fefae 100644 --- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java +++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java @@ -48,7 +48,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entitiy.entityView.TbEntityViewService; +import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -132,14 +132,16 @@ public class EntityViewController extends BaseController { } @ApiOperation(value = "Save or update entity view (saveEntityView)", - notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION, + notes = ENTITY_VIEW_DESCRIPTION + MODEL_DESCRIPTION + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new Entity View entity." + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, produces = MediaType.APPLICATION_JSON_VALUE) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/entityView", method = RequestMethod.POST) @ResponseBody public EntityView saveEntityView( @ApiParam(value = "A JSON object representing the entity view.") - @RequestBody EntityView entityView) throws ThingsboardException { + @RequestBody EntityView entityView) throws Exception { entityView.setTenantId(getCurrentUser().getTenantId()); EntityView existingEntityView = null; if (entityView.getId() == null) { @@ -452,7 +454,8 @@ public class EntityViewController extends BaseController { EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); checkEntityViewId(entityViewId, Operation.READ); - return tbEntityViewService.assignEntityViewToEdge(getTenantId(), getCurrentUser().getCustomerId(), entityViewId, edge, getCurrentUser()); + return tbEntityViewService.assignEntityViewToEdge(getTenantId(), getCurrentUser().getCustomerId(), + entityViewId, edge, getCurrentUser()); } @ApiOperation(value = "Unassign entity view from edge (unassignEntityViewFromEdge)", @@ -476,7 +479,8 @@ public class EntityViewController extends BaseController { EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId)); EntityView entityView = checkEntityViewId(entityViewId, Operation.READ); - return tbEntityViewService.unassignEntityViewFromEdge(getTenantId(), entityView.getCustomerId(), entityView, edge, getCurrentUser()); + return tbEntityViewService.unassignEntityViewFromEdge(getTenantId(), entityView.getCustomerId(), entityView, + edge, getCurrentUser()); } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") diff --git a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java index ba3845127d..0040187f02 100644 --- a/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java +++ b/application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java @@ -43,10 +43,12 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entitiy.otaPackageController.TbOtaPackageService; +import org.thingsboard.server.service.entitiy.ota.TbOtaPackageService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.io.IOException; + import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; import static org.springframework.http.MediaType.MULTIPART_FORM_DATA_VALUE; import static org.thingsboard.server.controller.ControllerConstants.DEVICE_PROFILE_ID_PARAM_DESCRIPTION; @@ -158,8 +160,6 @@ public class OtaPackageController extends BaseController { checkEntity(otaPackageInfo.getId(), otaPackageInfo, Resource.OTA_PACKAGE); return tbOtaPackageService.save(otaPackageInfo, getCurrentUser()); - - } @ApiOperation(value = "Save OTA Package data (saveOtaPackageData)", @@ -176,19 +176,15 @@ public class OtaPackageController extends BaseController { @ApiParam(value = "OTA Package checksum algorithm.", allowableValues = OTA_PACKAGE_CHECKSUM_ALGORITHM_ALLOWABLE_VALUES) @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, @ApiParam(value = "OTA Package data.") - @RequestPart MultipartFile file) throws ThingsboardException { + @RequestPart MultipartFile file) throws ThingsboardException, IOException { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); OtaPackageInfo otaPackageInfo = checkOtaPackageInfoId(otaPackageId, Operation.READ); - try { - ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); - byte[] data = file.getBytes(); - return tbOtaPackageService.saveOtaPackageData(otaPackageInfo, checksum, checksumAlgorithm, - data, file.getOriginalFilename(), file.getContentType(), getCurrentUser()); - } catch (Exception e) { - throw handleException(e); - } + ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); + byte[] data = file.getBytes(); + return tbOtaPackageService.saveOtaPackageData(otaPackageInfo, checksum, checksumAlgorithm, + data, file.getOriginalFilename(), file.getContentType(), getCurrentUser()); } @ApiOperation(value = "Get OTA Package Infos (getOtaPackages)", @@ -260,7 +256,6 @@ public class OtaPackageController extends BaseController { checkParameter(OTA_PACKAGE_ID, strOtaPackageId); OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId)); OtaPackageInfo otaPackageInfo = checkOtaPackageInfoId(otaPackageId, Operation.DELETE); - tbOtaPackageService.delete(otaPackageInfo, getCurrentUser()); } diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueController.java b/application/src/main/java/org/thingsboard/server/controller/QueueController.java index 39a971e6ed..3b37e6727f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueController.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.controller; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -37,6 +39,22 @@ import org.thingsboard.server.service.security.permission.Resource; import java.util.UUID; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_DATA_PARAMETERS; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_NUMBER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.PAGE_SIZE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_ID_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_NAME_PARAM_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SERVICE_TYPE_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; +import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SORT_PROPERTY_DESCRIPTION; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH; +import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; + @RestController @TbCoreComponent @RequestMapping("/api") @@ -45,82 +63,100 @@ public class QueueController extends BaseController { private final TbQueueService tbQueueService; + @ApiOperation(value = "Get Queues (getTenantQueuesByServiceType)", + notes = "Returns a page of queues registered in the platform. " + + PAGE_DATA_PARAMETERS + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/queues", params = {"serviceType", "pageSize", "page"}, method = RequestMethod.GET) @ResponseBody - public PageData getTenantQueuesByServiceType(@RequestParam String serviceType, + public PageData getTenantQueuesByServiceType(@ApiParam(value = QUEUE_SERVICE_TYPE_DESCRIPTION, allowableValues = QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES, required = true) + @RequestParam String serviceType, + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, + @ApiParam(value = QUEUE_QUEUE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = QUEUE_SORT_PROPERTY_ALLOWABLE_VALUES) @RequestParam(required = false) String sortProperty, + @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES) @RequestParam(required = false) String sortOrder) throws ThingsboardException { checkParameter("serviceType", serviceType); - try { - PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - ServiceType type = ServiceType.valueOf(serviceType); - switch (type) { - case TB_RULE_ENGINE: - return queueService.findQueuesByTenantId(getTenantId(), pageLink); - default: - return new PageData<>(); - } - } catch (Exception e) { - throw handleException(e); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + ServiceType type = ServiceType.valueOf(serviceType); + switch (type) { + case TB_RULE_ENGINE: + return queueService.findQueuesByTenantId(getTenantId(), pageLink); + default: + return new PageData<>(); } } + @ApiOperation(value = "Get Queue (getQueueById)", + notes = "Fetch the Queue object based on the provided Queue Id. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/queues/{queueId}", method = RequestMethod.GET) @ResponseBody - public Queue getQueueById(@PathVariable("queueId") String queueIdStr) throws ThingsboardException { + public Queue getQueueById(@ApiParam(value = QUEUE_ID_PARAM_DESCRIPTION) + @PathVariable("queueId") String queueIdStr) throws ThingsboardException { checkParameter("queueId", queueIdStr); - try { - QueueId queueId = new QueueId(UUID.fromString(queueIdStr)); - checkQueueId(queueId, Operation.READ); - return checkNotNull(queueService.findQueueById(getTenantId(), queueId)); - } catch ( - Exception e) { - throw handleException(e); - } + QueueId queueId = new QueueId(UUID.fromString(queueIdStr)); + checkQueueId(queueId, Operation.READ); + return checkNotNull(queueService.findQueueById(getTenantId(), queueId)); + } + + @ApiOperation(value = "Get Queue (getQueueByName)", + notes = "Fetch the Queue object based on the provided Queue name. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") + @RequestMapping(value = "/queues/name/{queueName}", method = RequestMethod.GET) + @ResponseBody + public Queue getQueueByName(@ApiParam(value = QUEUE_NAME_PARAM_DESCRIPTION) + @PathVariable("queueName") String queueName) throws ThingsboardException { + checkParameter("queueName", queueName); + return checkNotNull(queueService.findQueueByTenantIdAndName(getTenantId(), queueName)); } + @ApiOperation(value = "Create Or Update Queue (saveQueue)", + notes = "Create or update the Queue. When creating queue, platform generates Queue Id as " + UUID_WIKI_LINK + + "Specify existing Queue id to update the queue. " + + "Referencing non-existing Queue Id will cause 'Not Found' error." + + "\n\nQueue name is unique in the scope of sysadmin. " + + "Remove 'id', 'tenantId' from the request body example (below) to create new Queue entity. " + + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/queues", params = {"serviceType"}, method = RequestMethod.POST) @ResponseBody - public Queue saveQueue(@RequestBody Queue queue, + + public Queue saveQueue(@ApiParam(value = "A JSON value representing the queue.") + @RequestBody Queue queue, + @ApiParam(value = QUEUE_SERVICE_TYPE_DESCRIPTION, allowableValues = QUEUE_SERVICE_TYPE_ALLOWABLE_VALUES, required = true) @RequestParam String serviceType) throws ThingsboardException { checkParameter("serviceType", serviceType); - try { - queue.setTenantId(getCurrentUser().getTenantId()); + queue.setTenantId(getCurrentUser().getTenantId()); - checkEntity(queue.getId(), queue, Resource.QUEUE); + checkEntity(queue.getId(), queue, Resource.QUEUE); - ServiceType type = ServiceType.valueOf(serviceType); - switch (type) { - case TB_RULE_ENGINE: - queue.setTenantId(getTenantId()); - Queue savedQueue = tbQueueService.saveQueue(queue); - checkNotNull(savedQueue); - return savedQueue; - default: - return null; - } - } catch (Exception e) { - throw handleException(e); + ServiceType type = ServiceType.valueOf(serviceType); + switch (type) { + case TB_RULE_ENGINE: + queue.setTenantId(getTenantId()); + Queue savedQueue = tbQueueService.saveQueue(queue); + checkNotNull(savedQueue); + return savedQueue; + default: + return null; } } + @ApiOperation(value = "Delete Queue (deleteQueue)", notes = "Deletes the Queue. " + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @RequestMapping(value = "/queues/{queueId}", method = RequestMethod.DELETE) @ResponseBody - public void deleteQueue(@PathVariable("queueId") String queueIdStr) throws ThingsboardException { + public void deleteQueue(@ApiParam(value = QUEUE_ID_PARAM_DESCRIPTION) + @PathVariable("queueId") String queueIdStr) throws ThingsboardException { checkParameter("queueId", queueIdStr); - try { - QueueId queueId = new QueueId(toUUID(queueIdStr)); - checkQueueId(queueId, Operation.DELETE); - tbQueueService.deleteQueue(getTenantId(), queueId); - } catch (Exception e) { - throw handleException(e); - } + QueueId queueId = new QueueId(toUUID(queueIdStr)); + checkQueueId(queueId, Operation.DELETE); + tbQueueService.deleteQueue(getTenantId(), queueId); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 190d68e79e..faa1711fd9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -70,6 +70,7 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -226,18 +227,19 @@ public class RuleChainController extends BaseController { "The newly created Rule Chain Id will be present in the response. " + "Specify existing Rule Chain id to update the rule chain. " + "Referencing non-existing rule chain Id will cause 'Not Found' error." + - "\n\n" + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH) + "\n\n" + RULE_CHAIN_DESCRIPTION + + "Remove 'id', 'tenantId' from the request body example (below) to create new Rule Chain entity." + + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain", method = RequestMethod.POST) @ResponseBody public RuleChain saveRuleChain( @ApiParam(value = "A JSON value representing the rule chain.") - @RequestBody RuleChain ruleChain) throws ThingsboardException { - + @RequestBody RuleChain ruleChain) throws Exception { ruleChain.setTenantId(getCurrentUser().getTenantId()); checkEntity(ruleChain.getId(), ruleChain, Resource.RULE_CHAIN); return tbRuleChainService.save(ruleChain, getCurrentUser()); - } + } @ApiOperation(value = "Create Default Rule Chain", notes = "Create rule chain from template, based on the specified name in the request. " + @@ -247,12 +249,11 @@ public class RuleChainController extends BaseController { @ResponseBody public RuleChain saveRuleChain( @ApiParam(value = "A JSON value representing the request.") - @RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException { - + @RequestBody DefaultRuleChainCreateRequest request) throws Exception { checkNotNull(request); checkParameter(request.getName(), "name"); return tbRuleChainService.saveDefaultByName(getTenantId(), request, getCurrentUser()); - } + } @ApiOperation(value = "Set Root Rule Chain (setRootRuleChain)", notes = "Makes the rule chain to be root rule chain. Updates previous root rule chain as well. " + TENANT_AUTHORITY_PARAGRAPH) @@ -265,8 +266,7 @@ public class RuleChainController extends BaseController { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.WRITE); - return tbRuleChainService.setRootRuleChain(getTenantId(),ruleChain, getCurrentUser()); - + return tbRuleChainService.setRootRuleChain(getTenantId(), ruleChain, getCurrentUser()); } @ApiOperation(value = "Update Rule Chain Metadata", @@ -279,7 +279,7 @@ public class RuleChainController extends BaseController { @RequestBody RuleChainMetaData ruleChainMetaData, @ApiParam(value = "Update related rule nodes.") @RequestParam(value = "updateRelated", required = false, defaultValue = "true") boolean updateRelated - ) throws ThingsboardException { + ) throws Exception { TenantId tenantId = getTenantId(); if (debugPerTenantEnabled) { ConcurrentMap debugPerTenantLimits = actorContext.getDebugPerTenantLimits(); @@ -290,9 +290,8 @@ public class RuleChainController extends BaseController { } RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE); - return tbRuleChainService.saveRuleChainMetaData(tenantId, ruleChain, ruleChainMetaData, updateRelated, - getCurrentUser()); - } + return tbRuleChainService.saveRuleChainMetaData(tenantId, ruleChain, ruleChainMetaData, updateRelated, getCurrentUser()); + } @ApiOperation(value = "Get Rule Chains (getRuleChains)", notes = "Returns a page of Rule Chains owned by tenant. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) @@ -335,11 +334,10 @@ public class RuleChainController extends BaseController { @ApiParam(value = RULE_CHAIN_ID_PARAM_DESCRIPTION) @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException { checkParameter(RULE_CHAIN_ID, strRuleChainId); - RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.DELETE); tbRuleChainService.delete(ruleChain, getCurrentUser()); - } + } @ApiOperation(value = "Get latest input message (getLatestRuleNodeDebugInput)", notes = "Gets the input message from the debug events for specified Rule Chain Id. " + @@ -532,7 +530,7 @@ public class RuleChainController extends BaseController { RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.READ); return tbRuleChainService.assignRuleChainToEdge(getTenantId(), ruleChain, edge, getCurrentUser()); - } + } @ApiOperation(value = "Unassign rule chain from edge (unassignRuleChainFromEdge)", notes = "Clears assignment of the rule chain to the edge. " + @@ -554,7 +552,7 @@ public class RuleChainController extends BaseController { RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.READ); return tbRuleChainService.unassignRuleChainFromEdge(getTenantId(), ruleChain, edge, getCurrentUser()); - } + } @ApiOperation(value = "Get Edge Rule Chains (getEdgeRuleChains)", notes = "Returns a page of Rule Chains assigned to the specified edge. " + RULE_CHAIN_DESCRIPTION + PAGE_DATA_PARAMETERS + TENANT_AUTHORITY_PARAGRAPH) @@ -612,7 +610,7 @@ public class RuleChainController extends BaseController { RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.WRITE); return tbRuleChainService.setAutoAssignToEdgeRuleChain(getTenantId(), ruleChain, getCurrentUser()); - } + } @ApiOperation(value = "Unset Auto Assign To Edge Rule Chain (unsetAutoAssignToEdgeRuleChain)", notes = "Removes the rule chain from the list of rule chains that are going to be automatically assigned for any new edge that will be created. " + @@ -625,9 +623,8 @@ public class RuleChainController extends BaseController { checkParameter(RULE_CHAIN_ID, strRuleChainId); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.WRITE); - return tbRuleChainService.unsetAutoAssignToEdgeRuleChain(getTenantId(), ruleChain, getCurrentUser()); - } + } // TODO: @voba refactor this - add new config to edge rule chain to set it as auto-assign @ApiOperation(value = "Get Auto Assign To Edge Rule Chains (getAutoAssignToEdgeRuleChains)", diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 120449c137..ec0b530117 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -71,7 +71,7 @@ import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LI @RequiredArgsConstructor public class TbResourceController extends BaseController { - private final TbResourceService tbResourceService; + private final TbResourceService tbResourceService; public static final String RESOURCE_ID = "resourceId"; @@ -139,14 +139,16 @@ public class TbResourceController extends BaseController { "The newly created Resource id will be present in the response. " + "Specify existing Resource id to update the Resource. " + "Referencing non-existing Resource Id will cause 'Not Found' error. " + - "\n\nResource combination of the title with the key is unique in the scope of tenant. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, + "\n\nResource combination of the title with the key is unique in the scope of tenant. " + + "Remove 'id', 'tenantId' from the request body example (below) to create new Resource entity." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH, produces = "application/json", consumes = "application/json") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource", method = RequestMethod.POST) @ResponseBody public TbResource saveResource(@ApiParam(value = "A JSON value representing the Resource.") - @RequestBody TbResource resource) throws ThingsboardException { + @RequestBody TbResource resource) throws Exception { resource.setTenantId(getTenantId()); checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); return tbResourceService.save(resource, getCurrentUser()); diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 5ac3355bd4..b9c929f909 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -888,48 +888,29 @@ public class TelemetryController extends BaseController { } private void logTimeseriesDeleted(SecurityUser user, EntityId entityId, List keys, long startTs, long endTs, Throwable e) { - try { - logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.TIMESERIES_DELETED, toException(e), - keys, startTs, endTs); - } catch (ThingsboardException te) { - log.warn("Failed to log timeseries delete", te); - } + notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_DELETED, user, + toException(e), keys, startTs, endTs); } private void logTelemetryUpdated(SecurityUser user, EntityId entityId, List telemetry, Throwable e) { - try { - logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.TIMESERIES_UPDATED, toException(e), telemetry); - } catch (ThingsboardException te) { - log.warn("Failed to log telemetry update"); - } + notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.TIMESERIES_UPDATED, user, + toException(e), telemetry); } private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) { - try { - logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_DELETED, toException(e), - scope, keys); - } catch (ThingsboardException te) { - log.warn("Failed to log attributes delete", te); - } + notificationEntityService.logEntityAction(user.getTenantId(), (UUIDBased & EntityId) entityId, + ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); } private void logAttributesUpdated(SecurityUser user, EntityId entityId, String scope, List attributes, Throwable e) { - try { - logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_UPDATED, toException(e), - scope, attributes); - } catch (ThingsboardException te) { - log.warn("Failed to log attributes update", te); - } + notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_UPDATED, user, + toException(e), scope, attributes); } private void logAttributesRead(SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) { - try { - logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_READ, toException(e), - scope, keys); - } catch (ThingsboardException te) { - log.warn("Failed to log attributes read", te); - } + notificationEntityService.logEntityAction(user.getTenantId(), entityId, ActionType.ATTRIBUTES_READ, user, + toException(e), scope, keys); } private ListenableFuture> mergeAllAttributesFutures(List>> futures) { diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 5e18f1e3a5..b44c5327cd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -115,12 +115,13 @@ public class TenantController extends BaseController { "The newly created Tenant Id will be present in the response. " + "Specify existing Tenant Id id to update the Tenant. " + "Referencing non-existing Tenant Id will cause 'Not Found' error." + + "Remove 'id', 'tenantId' from the request body example (below) to create new Tenant entity." + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenant", method = RequestMethod.POST) @ResponseBody public Tenant saveTenant(@ApiParam(value = "A JSON value representing the tenant.") - @RequestBody Tenant tenant) throws ThingsboardException { + @RequestBody Tenant tenant) throws Exception { checkEntity(tenant.getId(), tenant, Resource.TENANT); return tbTenantService.save(tenant); } @@ -131,7 +132,7 @@ public class TenantController extends BaseController { @RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) public void deleteTenant(@ApiParam(value = TENANT_ID_PARAM_DESCRIPTION) - @PathVariable(TENANT_ID) String strTenantId) throws ThingsboardException { + @PathVariable(TENANT_ID) String strTenantId) throws Exception { checkParameter(TENANT_ID, strTenantId); TenantId tenantId = TenantId.fromUUID(toUUID(strTenantId)); Tenant tenant = checkTenantId(tenantId, Operation.DELETE); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java index 40956d0fcc..e5a33da948 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantProfileController.java @@ -32,13 +32,11 @@ import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entitiy.tenant_profile.TbTenantProfileService; +import org.thingsboard.server.service.entitiy.tenant.profile.TbTenantProfileService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -130,7 +128,6 @@ public class TenantProfileController extends BaseController { "{\n" + " \"name\": \"Default\",\n" + " \"description\": \"Default tenant profile\",\n" + - " \"isolatedTbCore\": false,\n" + " \"isolatedTbRuleEngine\": false,\n" + " \"profileData\": {\n" + " \"configuration\": {\n" + @@ -167,30 +164,23 @@ public class TenantProfileController extends BaseController { " \"default\": true\n" + "}" + MARKDOWN_CODE_BLOCK_END + + "Remove 'id', from the request body example (below) to create new Tenant Profile entity." + SYSTEM_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenantProfile", method = RequestMethod.POST) @ResponseBody - public TenantProfile saveTenantProfile( - @ApiParam(value = "A JSON value representing the tenant profile.") - @RequestBody TenantProfile tenantProfile) throws ThingsboardException { + public TenantProfile saveTenantProfile(@ApiParam(value = "A JSON value representing the tenant profile.") + @RequestBody TenantProfile tenantProfile) throws ThingsboardException { try { - boolean newTenantProfile = tenantProfile.getId() == null; TenantProfile oldProfile; - if (newTenantProfile) { - accessControlService - .checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE); + if (tenantProfile.getId() == null) { + accessControlService.checkPermission(getCurrentUser(), Resource.TENANT_PROFILE, Operation.CREATE); oldProfile = null; } else { oldProfile = checkTenantProfileId(tenantProfile.getId(), Operation.WRITE); } - tenantProfile = checkNotNull(tbTenantProfileService.saveTenantProfile(getTenantId(), tenantProfile, oldProfile)); - tenantProfileCache.put(tenantProfile); - tbClusterService.onTenantProfileChange(tenantProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, tenantProfile.getId(), - newTenantProfile ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - return tenantProfile; + return tbTenantProfileService.save(getTenantId(), tenantProfile, oldProfile); } catch (Exception e) { throw handleException(e); } @@ -201,15 +191,13 @@ public class TenantProfileController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenantProfile/{tenantProfileId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) - public void deleteTenantProfile( - @ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) - @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { - checkParameter("tenantProfileId", strTenantProfileId); + public void deleteTenantProfile(@ApiParam(value = TENANT_PROFILE_ID_PARAM_DESCRIPTION) + @PathVariable("tenantProfileId") String strTenantProfileId) throws ThingsboardException { try { + checkParameter("tenantProfileId", strTenantProfileId); TenantProfileId tenantProfileId = new TenantProfileId(toUUID(strTenantProfileId)); TenantProfile profile = checkTenantProfileId(tenantProfileId, Operation.DELETE); - tenantProfileService.deleteTenantProfile(getTenantId(), tenantProfileId); - tbClusterService.onTenantProfileDelete(profile, null); + tbTenantProfileService.delete(getTenantId(), profile); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFaConfigController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java similarity index 99% rename from application/src/main/java/org/thingsboard/server/controller/TwoFaConfigController.java rename to application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java index 4b34b95f59..5aad5c9503 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFaConfigController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthConfigController.java @@ -50,7 +50,7 @@ import static org.thingsboard.server.controller.ControllerConstants.NEW_LINE; @RequestMapping("/api/2fa") @TbCoreComponent @RequiredArgsConstructor -public class TwoFaConfigController extends BaseController { +public class TwoFactorAuthConfigController extends BaseController { private final TwoFaConfigManager twoFaConfigManager; private final TwoFactorAuthService twoFactorAuthService; diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index d6fda64d31..e96a73b65c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -102,9 +102,9 @@ public class UserController extends BaseController { @ApiOperation(value = "Get User (getUserById)", notes = "Fetch the User object based on the provided User Id. " + - "If the user has the authority of 'SYS_ADMIN', the server does not perform additional checks. " + - "If the user has the authority of 'TENANT_ADMIN', the server checks that the requested user is owned by the same tenant. " + - "If the user has the authority of 'CUSTOMER_USER', the server checks that the requested user is owned by the same customer.") + "If the user has the authority of 'SYS_ADMIN', the server does not perform additional checks. " + + "If the user has the authority of 'TENANT_ADMIN', the server checks that the requested user is owned by the same tenant. " + + "If the user has the authority of 'CUSTOMER_USER', the server checks that the requested user is owned by the same customer.") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) @ResponseBody @@ -176,7 +176,9 @@ public class UserController extends BaseController { "The newly created User Id will be present in the response. " + "Specify existing User Id to update the device. " + "Referencing non-existing User Id will cause 'Not Found' error." + - "\n\nDevice email is unique for entire platform setup.") + "\n\nDevice email is unique for entire platform setup." + + "Remove 'id', 'tenantId' and optionally 'customerId' from the request body example (below) to create new User entity." + + "\n\nAvailable for users with 'SYS_ADMIN', 'TENANT_ADMIN' or 'CUSTOMER_USER' authority.") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user", method = RequestMethod.POST) @ResponseBody @@ -190,7 +192,7 @@ public class UserController extends BaseController { } checkEntity(user.getId(), user, Resource.USER); return tbUserService.save(getTenantId(), getCurrentUser().getCustomerId(), user, sendActivationMail, request, getCurrentUser()); - } + } @ApiOperation(value = "Send or re-send the activation email", notes = "Force send the activation email to the user. Useful to resend the email if user has accidentally deleted it. " + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java index 35791ea762..725d8ca2b4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeInfo; +import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -52,7 +53,7 @@ import static org.thingsboard.server.controller.ControllerConstants.WIDGET_TYPE_ @RestController @TbCoreComponent @RequestMapping("/api") -public class WidgetTypeController extends BaseController { +public class WidgetTypeController extends AutoCommitController { private static final String WIDGET_TYPE_DESCRIPTION = "Widget Type represents the template for widget creation. Widget Type and Widget are similar to class and object in OOP theory."; private static final String WIDGET_TYPE_DETAILS_DESCRIPTION = "Widget Type Details extend Widget Type and add image and description properties. " + @@ -84,8 +85,9 @@ public class WidgetTypeController extends BaseController { "Specify existing Widget Type id to update the Widget Type. " + "Referencing non-existing Widget Type Id will cause 'Not Found' error." + "\n\nWidget Type alias is unique in the scope of Widget Bundle. " + - "Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create request is sent by user with 'SYS_ADMIN' authority." - + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + "Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create request is sent by user with 'SYS_ADMIN' authority." + + "Remove 'id', 'tenantId' rom the request body example (below) to create new Widget Type entity." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/widgetType", method = RequestMethod.POST) @ResponseBody @@ -93,15 +95,23 @@ public class WidgetTypeController extends BaseController { @ApiParam(value = "A JSON value representing the Widget Type Details.", required = true) @RequestBody WidgetTypeDetails widgetTypeDetails) throws ThingsboardException { try { - if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { + var currentUser = getCurrentUser(); + if (Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { widgetTypeDetails.setTenantId(TenantId.SYS_TENANT_ID); } else { - widgetTypeDetails.setTenantId(getCurrentUser().getTenantId()); + widgetTypeDetails.setTenantId(currentUser.getTenantId()); } checkEntity(widgetTypeDetails.getId(), widgetTypeDetails, Resource.WIDGET_TYPE); WidgetTypeDetails savedWidgetTypeDetails = widgetTypeService.saveWidgetType(widgetTypeDetails); + if (!Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId(), widgetTypeDetails.getBundleAlias()); + if (widgetsBundle != null) { + autoCommit(currentUser, widgetsBundle.getId()); + } + } + sendEntityNotificationMsg(getTenantId(), savedWidgetTypeDetails.getId(), widgetTypeDetails.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); @@ -121,9 +131,17 @@ public class WidgetTypeController extends BaseController { @PathVariable("widgetTypeId") String strWidgetTypeId) throws ThingsboardException { checkParameter("widgetTypeId", strWidgetTypeId); try { + var currentUser = getCurrentUser(); WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId)); - checkWidgetTypeId(widgetTypeId, Operation.DELETE); - widgetTypeService.deleteWidgetType(getCurrentUser().getTenantId(), widgetTypeId); + WidgetTypeDetails wtd = checkWidgetTypeId(widgetTypeId, Operation.DELETE); + widgetTypeService.deleteWidgetType(currentUser.getTenantId(), widgetTypeId); + + if (wtd != null && !Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(wtd.getTenantId(), wtd.getBundleAlias()); + if (widgetsBundle != null) { + autoCommit(currentUser, widgetsBundle.getId()); + } + } sendEntityNotificationMsg(getTenantId(), widgetTypeId, EdgeEventActionType.DELETED); diff --git a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java index 4a69b203ed..23ec498d0e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java +++ b/application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.entitiy.widgetsBundle.TbWidgetsBundleService; +import org.thingsboard.server.service.entitiy.widgets.bundle.TbWidgetsBundleService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -89,24 +89,25 @@ public class WidgetsBundleController extends BaseController { "Specify existing Widget Bundle id to update the Widget Bundle. " + "Referencing non-existing Widget Bundle Id will cause 'Not Found' error." + "\n\nWidget Bundle alias is unique in the scope of tenant. " + - "Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create bundle request is sent by user with 'SYS_ADMIN' authority." - + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) + "Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create bundle request is sent by user with 'SYS_ADMIN' authority." + + "Remove 'id', 'tenantId' from the request body example (below) to create new Widgets Bundle entity." + + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/widgetsBundle", method = RequestMethod.POST) @ResponseBody public WidgetsBundle saveWidgetsBundle( @ApiParam(value = "A JSON value representing the Widget Bundle.", required = true) - @RequestBody WidgetsBundle widgetsBundle) throws ThingsboardException { - - if (Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { - widgetsBundle.setTenantId(TenantId.SYS_TENANT_ID); - } else { - widgetsBundle.setTenantId(getCurrentUser().getTenantId()); - } + @RequestBody WidgetsBundle widgetsBundle) throws Exception { + var currentUser = getCurrentUser(); + if (Authority.SYS_ADMIN.equals(currentUser.getAuthority())) { + widgetsBundle.setTenantId(TenantId.SYS_TENANT_ID); + } else { + widgetsBundle.setTenantId(currentUser.getTenantId()); + } - checkEntity(widgetsBundle.getId(), widgetsBundle, Resource.WIDGETS_BUNDLE); + checkEntity(widgetsBundle.getId(), widgetsBundle, Resource.WIDGETS_BUNDLE); - return tbWidgetsBundleService.save(widgetsBundle, getCurrentUser()); + return tbWidgetsBundleService.save(widgetsBundle, currentUser); } @ApiOperation(value = "Delete widgets bundle (deleteWidgetsBundle)", @@ -120,7 +121,7 @@ public class WidgetsBundleController extends BaseController { checkParameter("widgetsBundleId", strWidgetsBundleId); WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId)); WidgetsBundle widgetsBundle = checkWidgetsBundleId(widgetsBundleId, Operation.DELETE); - tbWidgetsBundleService.delete(widgetsBundle, getCurrentUser()); + tbWidgetsBundleService.delete(widgetsBundle); } @ApiOperation(value = "Get Widget Bundles (getWidgetsBundles)", diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java index 5f0ea800dd..578bfa5662 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java @@ -16,12 +16,12 @@ package org.thingsboard.server.controller.plugin; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.PongMessage; import org.springframework.web.socket.TextMessage; @@ -34,10 +34,10 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.config.WebSocketConfiguration; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; -import org.thingsboard.server.service.telemetry.DefaultTelemetryWebSocketService; import org.thingsboard.server.service.telemetry.SessionEvent; import org.thingsboard.server.service.telemetry.TelemetryWebSocketMsgEndpoint; import org.thingsboard.server.service.telemetry.TelemetryWebSocketService; @@ -72,22 +72,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr @Autowired private TelemetryWebSocketService webSocketService; + @Autowired + private TbTenantProfileCache tenantProfileCache; + @Value("${server.ws.send_timeout:5000}") private long sendTimeout; - @Value("${server.ws.limits.max_sessions_per_tenant:0}") - private int maxSessionsPerTenant; - @Value("${server.ws.limits.max_sessions_per_customer:0}") - private int maxSessionsPerCustomer; - @Value("${server.ws.limits.max_sessions_per_regular_user:0}") - private int maxSessionsPerRegularUser; - @Value("${server.ws.limits.max_sessions_per_public_user:0}") - private int maxSessionsPerPublicUser; - @Value("${server.ws.limits.max_queue_per_ws_session:1000}") - private int maxMsgQueuePerSession; - - @Value("${server.ws.limits.max_updates_per_session:}") - private String perSessionUpdatesConfiguration; - @Value("${server.ws.ping_timeout:30000}") private long pingTimeout; @@ -144,10 +133,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr String internalSessionId = session.getId(); TelemetryWebSocketSessionRef sessionRef = toRef(session); String externalSessionId = sessionRef.getSessionId(); + if (!checkLimits(session, sessionRef)) { return; } - internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, maxMsgQueuePerSession)); + var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); + internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, tenantProfileConfiguration.getWsMsgQueueLimitPerSession() > 0 ? + tenantProfileConfiguration.getWsMsgQueueLimitPerSession() : 500)); externalSessionMap.put(externalSessionId, internalSessionId); processInWebSocketService(sessionRef, SessionEvent.onEstablished()); @@ -323,8 +315,9 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr if (internalId != null) { SessionMetaData sessionMd = internalSessionMap.get(internalId); if (sessionMd != null) { - if (!StringUtils.isEmpty(perSessionUpdatesConfiguration)) { - TbRateLimits rateLimits = perSessionUpdateLimits.computeIfAbsent(sessionRef.getSessionId(), sid -> new TbRateLimits(perSessionUpdatesConfiguration)); + var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); + if (StringUtils.isNotEmpty(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit())) { + TbRateLimits rateLimits = perSessionUpdateLimits.computeIfAbsent(sessionRef.getSessionId(), sid -> new TbRateLimits(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit())); if (!rateLimits.tryConsume()) { if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) { log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached" @@ -336,6 +329,8 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr log.debug("[{}][{}][{}] Session is no longer blacklisted.", sessionRef.getSecurityCtx().getTenantId(), sessionRef.getSecurityCtx().getId(), externalId); blacklistedSessions.remove(externalId); } + } else { + perSessionUpdateLimits.remove(sessionRef.getSessionId()); } sessionMd.sendMsg(msg); } else { @@ -380,11 +375,17 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } private boolean checkLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) throws Exception { + var tenantProfileConfiguration = + tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); + if (tenantProfileConfiguration == null) { + return true; + } + String sessionId = session.getId(); - if (maxSessionsPerTenant > 0) { + if (tenantProfileConfiguration.getMaxWsSessionsPerTenant() > 0) { Set tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); synchronized (tenantSessions) { - if (tenantSessions.size() < maxSessionsPerTenant) { + if (tenantSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerTenant()) { tenantSessions.add(sessionId); } else { log.info("[{}][{}][{}] Failed to start session. Max tenant sessions limit reached" @@ -396,10 +397,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } if (sessionRef.getSecurityCtx().isCustomerUser()) { - if (maxSessionsPerCustomer > 0) { + if (tenantProfileConfiguration.getMaxWsSessionsPerCustomer() > 0) { Set customerSessions = customerSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); synchronized (customerSessions) { - if (customerSessions.size() < maxSessionsPerCustomer) { + if (customerSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerCustomer()) { customerSessions.add(sessionId); } else { log.info("[{}][{}][{}] Failed to start session. Max customer sessions limit reached" @@ -409,10 +410,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } } - if (maxSessionsPerRegularUser > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + if (tenantProfileConfiguration.getMaxWsSessionsPerRegularUser() > 0 + && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { Set regularUserSessions = regularUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); synchronized (regularUserSessions) { - if (regularUserSessions.size() < maxSessionsPerRegularUser) { + if (regularUserSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerRegularUser()) { regularUserSessions.add(sessionId); } else { log.info("[{}][{}][{}] Failed to start session. Max regular user sessions limit reached" @@ -422,10 +424,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } } } - if (maxSessionsPerPublicUser > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + if (tenantProfileConfiguration.getMaxWsSessionsPerPublicUser() > 0 + && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { Set publicUserSessions = publicUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); synchronized (publicUserSessions) { - if (publicUserSessions.size() < maxSessionsPerPublicUser) { + if (publicUserSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerPublicUser()) { publicUserSessions.add(sessionId); } else { log.info("[{}][{}][{}] Failed to start session. Max public user sessions limit reached" @@ -440,29 +443,31 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr } private void cleanupLimits(WebSocketSession session, TelemetryWebSocketSessionRef sessionRef) { + var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); + String sessionId = session.getId(); perSessionUpdateLimits.remove(sessionRef.getSessionId()); blacklistedSessions.remove(sessionRef.getSessionId()); - if (maxSessionsPerTenant > 0) { + if (tenantProfileConfiguration.getMaxWsSessionsPerTenant() > 0) { Set tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); synchronized (tenantSessions) { tenantSessions.remove(sessionId); } } if (sessionRef.getSecurityCtx().isCustomerUser()) { - if (maxSessionsPerCustomer > 0) { + if (tenantProfileConfiguration.getMaxWsSessionsPerCustomer() > 0) { Set customerSessions = customerSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); synchronized (customerSessions) { customerSessions.remove(sessionId); } } - if (maxSessionsPerRegularUser > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + if (tenantProfileConfiguration.getMaxWsSessionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { Set regularUserSessions = regularUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); synchronized (regularUserSessions) { regularUserSessions.remove(sessionId); } } - if (maxSessionsPerPublicUser > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + if (tenantProfileConfiguration.getMaxWsSessionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { Set publicUserSessions = publicUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); synchronized (publicUserSessions) { publicUserSessions.remove(sessionId); diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java index b02f85f84a..208fdead93 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardCredentialsExpiredResponse.java @@ -34,7 +34,7 @@ public class ThingsboardCredentialsExpiredResponse extends ThingsboardErrorRespo return new ThingsboardCredentialsExpiredResponse(message, resetToken); } - @ApiModelProperty(position = 5, value = "Password reset token", readOnly = true) + @ApiModelProperty(position = 5, value = "Password reset token", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public String getResetToken() { return resetToken; } diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java index 09fce98985..7cf2ca87f1 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponse.java @@ -46,12 +46,12 @@ public class ThingsboardErrorResponse { return new ThingsboardErrorResponse(message, errorCode, status); } - @ApiModelProperty(position = 1, value = "HTTP Response Status Code", example = "401", readOnly = true) + @ApiModelProperty(position = 1, value = "HTTP Response Status Code", example = "401", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public Integer getStatus() { return status.value(); } - @ApiModelProperty(position = 2, value = "Error message", example = "Authentication failed", readOnly = true) + @ApiModelProperty(position = 2, value = "Error message", example = "Authentication failed", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public String getMessage() { return message; } @@ -69,12 +69,12 @@ public class ThingsboardErrorResponse { "\n\n* `34` - Too many updates (Too many updates over Websocket session)" + "\n\n* `40` - Subscription violation (HTTP: 403 - Forbidden)", example = "10", dataType = "integer", - readOnly = true) + accessMode = ApiModelProperty.AccessMode.READ_ONLY) public ThingsboardErrorCode getErrorCode() { return errorCode; } - @ApiModelProperty(position = 4, value = "Timestamp", readOnly = true) + @ApiModelProperty(position = 4, value = "Timestamp", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public Date getTimestamp() { return timestamp; } diff --git a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java index b8c0656bd2..39064c210c 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java @@ -22,13 +22,14 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; +import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -41,13 +42,11 @@ import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.cluster.TbClusterService; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -@TbCoreComponent @Service @RequiredArgsConstructor @Slf4j @@ -212,7 +211,7 @@ public class EntityActionService { } } - public void logEntityAction(User user, I entityId, E entity, CustomerId customerId, + public void logEntityAction(User user, I entityId, E entity, CustomerId customerId, ActionType actionType, Exception e, Object... additionalInfo) { if (customerId == null || customerId.isNullUid()) { customerId = user.getCustomerId(); @@ -223,6 +222,9 @@ public class EntityActionService { auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); } + public void sendEntityNotificationMsgToEdge(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { + tbClusterService.sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); + } private T extractParameter(Class clazz, int index, Object... additionalInfo) { T result = null; @@ -267,4 +269,5 @@ public class EntityActionService { entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString()); } } + } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java new file mode 100644 index 0000000000..55b23a99cc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultRateLimitService.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.msg.tools.TbRateLimits; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +@Service +@RequiredArgsConstructor +public class DefaultRateLimitService implements RateLimitService { + + private final TbTenantProfileCache tenantProfileCache; + + private final Map> rateLimits = new ConcurrentHashMap<>(); + + @Override + public boolean checkEntityExportLimit(TenantId tenantId) { + return checkLimit(tenantId, "entityExport", DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit); + } + + @Override + public boolean checkEntityImportLimit(TenantId tenantId) { + return checkLimit(tenantId, "entityImport", DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit); + } + + private boolean checkLimit(TenantId tenantId, String rateLimitsKey, Function rateLimitConfigExtractor) { + String rateLimitConfig = tenantProfileCache.get(tenantId).getProfileConfiguration() + .map(rateLimitConfigExtractor).orElse(null); + + Map rateLimits = this.rateLimits.get(rateLimitsKey); + if (StringUtils.isEmpty(rateLimitConfig)) { + if (rateLimits != null) { + rateLimits.remove(tenantId); + if (rateLimits.isEmpty()) { + this.rateLimits.remove(rateLimitsKey); + } + } + return true; + } + + if (rateLimits == null) { + rateLimits = new ConcurrentHashMap<>(); + this.rateLimits.put(rateLimitsKey, rateLimits); + } + TbRateLimits rateLimit = rateLimits.get(tenantId); + if (rateLimit == null || !rateLimit.getConfiguration().equals(rateLimitConfig)) { + rateLimit = new TbRateLimits(rateLimitConfig); + rateLimits.put(tenantId, rateLimit); + } + + return rateLimit.tryConsume(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/TbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java similarity index 70% rename from application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/TbTenantProfileService.java rename to application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java index 66c3cb138c..d3d4244ca1 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/TbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.tenant_profile; +package org.thingsboard.server.service.apiusage; -import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; -public interface TbTenantProfileService { - TenantProfile saveTenantProfile(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile); +public interface RateLimitService { + + boolean checkEntityExportLimit(TenantId tenantId); + + boolean checkEntityImportLimit(TenantId tenantId); + } diff --git a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java index e9999d2dc1..ff89c3dd06 100644 --- a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java @@ -26,9 +26,9 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.service.entitiy.asset.TbAssetService; -import org.thingsboard.server.service.importing.AbstractBulkImportService; -import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.security.model.SecurityUser; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java index f279a29aac..7a95bf78f9 100644 --- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java @@ -161,20 +161,20 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe configurationDescriptor.set("nodeDefinition", node); scannedComponent.setConfigurationDescriptor(configurationDescriptor); scannedComponent.setClazz(clazzName); - log.info("Processing scanned component: {}", scannedComponent); + log.debug("Processing scanned component: {}", scannedComponent); } catch (Exception e) { log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e); throw new RuntimeException(e); } ComponentDescriptor persistedComponent = componentDescriptorService.findByClazz(TenantId.SYS_TENANT_ID, clazzName); if (persistedComponent == null) { - log.info("Persisting new component: {}", scannedComponent); + log.debug("Persisting new component: {}", scannedComponent); scannedComponent = componentDescriptorService.saveComponent(TenantId.SYS_TENANT_ID, scannedComponent); } else if (scannedComponent.equals(persistedComponent)) { - log.info("Component is already persisted: {}", persistedComponent); + log.debug("Component is already persisted: {}", persistedComponent); scannedComponent = persistedComponent; } else { - log.info("Component {} will be updated to {}", persistedComponent, scannedComponent); + log.debug("Component {} will be updated to {}", persistedComponent, scannedComponent); componentDescriptorService.deleteByClazz(TenantId.SYS_TENANT_ID, persistedComponent.getClazz()); scannedComponent.setId(persistedComponent.getId()); scannedComponent = componentDescriptorService.saveComponent(TenantId.SYS_TENANT_ID, scannedComponent); @@ -224,7 +224,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe @Override public void discoverComponents() { registerRuleNodeComponents(); - log.info("Found following definitions: {}", components.values()); + log.debug("Found following definitions: {}", components.values()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java index 713160d8fe..68c9e3117e 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java +++ b/application/src/main/java/org/thingsboard/server/service/device/ClaimDevicesServiceImpl.java @@ -28,6 +28,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; @@ -52,12 +53,10 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.queue.util.TbCoreComponent; import javax.annotation.Nullable; -import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.concurrent.ExecutionException; import static org.thingsboard.server.common.data.CacheConstants.CLAIM_DEVICES_CACHE; @@ -122,55 +121,57 @@ public class ClaimDevicesServiceImpl implements ClaimDevicesService { }, MoreExecutors.directExecutor()); } - private ClaimDataInfo getClaimData(Cache cache, Device device) throws ExecutionException, InterruptedException { + private ListenableFuture getClaimData(Cache cache, Device device) { List key = constructCacheKey(device.getId()); ClaimData claimDataFromCache = cache.get(key, ClaimData.class); if (claimDataFromCache != null) { - return new ClaimDataInfo(true, key, claimDataFromCache); + return Futures.immediateFuture(new ClaimDataInfo(true, key, claimDataFromCache)); } else { - Optional claimDataAttr = attributesService.find(device.getTenantId(), device.getId(), - DataConstants.SERVER_SCOPE, CLAIM_DATA_ATTRIBUTE_NAME).get(); - if (claimDataAttr.isPresent()) { - try { - ClaimData claimDataFromAttribute = mapper.readValue(claimDataAttr.get().getValueAsString(), ClaimData.class); + ListenableFuture> claimDataAttrFuture = attributesService.find(device.getTenantId(), device.getId(), + DataConstants.SERVER_SCOPE, CLAIM_DATA_ATTRIBUTE_NAME); + + return Futures.transform(claimDataAttrFuture, claimDataAttr -> { + if (claimDataAttr.isPresent()) { + ClaimData claimDataFromAttribute = JacksonUtil.fromString(claimDataAttr.get().getValueAsString(), ClaimData.class); return new ClaimDataInfo(false, key, claimDataFromAttribute); - } catch (IOException e) { - log.warn("Failed to read Claim Data [{}] from attribute!", claimDataAttr, e); } - } + return null; + }, MoreExecutors.directExecutor()); } - return null; } @Override - public ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException { + public ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) { Cache cache = cacheManager.getCache(CLAIM_DEVICES_CACHE); - ClaimDataInfo claimData = getClaimData(cache, device); - if (claimData != null) { - long currTs = System.currentTimeMillis(); - if (currTs > claimData.getData().getExpirationTime() || !secretKeyIsEmptyOrEqual(secretKey, claimData.getData().getSecretKey())) { - log.warn("The claiming timeout occurred or wrong 'secretKey' provided for the device [{}]", device.getName()); - if (claimData.isFromCache()) { - cache.evict(claimData.getKey()); + ListenableFuture claimDataFuture = getClaimData(cache, device); + + return Futures.transformAsync(claimDataFuture, claimData -> { + if (claimData != null) { + long currTs = System.currentTimeMillis(); + if (currTs > claimData.getData().getExpirationTime() || !secretKeyIsEmptyOrEqual(secretKey, claimData.getData().getSecretKey())) { + log.warn("The claiming timeout occurred or wrong 'secretKey' provided for the device [{}]", device.getName()); + if (claimData.isFromCache()) { + cache.evict(claimData.getKey()); + } + return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.FAILURE)); + } else { + if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { + device.setCustomerId(customerId); + Device savedDevice = deviceService.saveDevice(device); + clusterService.onDeviceUpdated(savedDevice, device); + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor()); + } + return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor()); } - return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.FAILURE)); } else { + log.warn("Failed to find the device's claiming message![{}]", device.getName()); if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - device.setCustomerId(customerId); - Device savedDevice = deviceService.saveDevice(device); - clusterService.onDeviceUpdated(savedDevice, device); - return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(savedDevice, ClaimResponse.SUCCESS), MoreExecutors.directExecutor()); + return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.FAILURE)); + } else { + return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.CLAIMED)); } - return Futures.transform(removeClaimingSavedData(cache, claimData, device), result -> new ClaimResult(null, ClaimResponse.CLAIMED), MoreExecutors.directExecutor()); } - } else { - log.warn("Failed to find the device's claiming message![{}]", device.getName()); - if (device.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.FAILURE)); - } else { - return Futures.immediateFuture(new ClaimResult(null, ClaimResponse.CLAIMED)); - } - } + }, MoreExecutors.directExecutor()); } private boolean secretKeyIsEmptyOrEqual(String secretKeyA, String secretKeyB) { diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java index 34c30d5225..8b2126905f 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java @@ -49,9 +49,9 @@ import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.service.entitiy.device.TbDeviceService; -import org.thingsboard.server.service.importing.AbstractBulkImportService; -import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.security.model.SecurityUser; import java.util.Collection; @@ -121,7 +121,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService { } entity.setDeviceProfileId(deviceProfile.getId()); - return tbDeviceService.saveDeviceWithCredentials(user.getTenantId(), entity, deviceCredentials, user); + return tbDeviceService.saveDeviceWithCredentials(entity, deviceCredentials, user); } @Override @@ -176,7 +176,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService { ObjectNode lwm2mCredentials = JacksonUtil.newObjectNode(); Set.of(BulkImportColumnType.LWM2M_CLIENT_SECURITY_CONFIG_MODE, BulkImportColumnType.LWM2M_BOOTSTRAP_SERVER_SECURITY_MODE, - BulkImportColumnType.LWM2M_SERVER_SECURITY_MODE).stream() + BulkImportColumnType.LWM2M_SERVER_SECURITY_MODE).stream() .map(fields::get) .filter(Objects::nonNull) .forEach(securityMode -> { @@ -233,7 +233,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); transportConfiguration.setBootstrap(Collections.emptyList()); - transportConfiguration.setClientLwM2mSettings(new OtherConfiguration(1,1,1, PowerMode.DRX, null, null, null, null, null)); + 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(); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index 6b5e519f80..5fa58a5bb5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -137,6 +137,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { case ENTITY_VIEW: case DASHBOARD: case RULE_CHAIN: + case OTA_PACKAGE: future = entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg); break; case CUSTOMER: @@ -144,6 +145,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { break; case WIDGETS_BUNDLE: case WIDGET_TYPE: + case QUEUE: future = entityProcessor.processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); break; case ALARM: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java index fd55e9b321..8372e2ba0e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java @@ -28,9 +28,9 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.service.entitiy.edge.TbEdgeService; -import org.thingsboard.server.service.importing.AbstractBulkImportService; -import org.thingsboard.server.service.importing.BulkImportColumnType; import org.thingsboard.server.service.security.model.SecurityUser; import java.util.Map; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index 04f8788503..d7e861a9fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -26,6 +26,8 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserService; @@ -41,6 +43,8 @@ import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.QueueEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.TelemetryEdgeProcessor; @@ -93,6 +97,12 @@ public class EdgeContextComponent { @Autowired private EdgeRequestsService edgeRequestsService; + @Autowired + private OtaPackageService otaPackageService; + + @Autowired + private QueueService queueService; + @Autowired private AlarmEdgeProcessor alarmProcessor; @@ -138,6 +148,12 @@ public class EdgeContextComponent { @Autowired private AdminSettingsEdgeProcessor adminSettingsProcessor; + @Autowired + private OtaPackageEdgeProcessor otaPackageEdgeProcessor; + + @Autowired + private QueueEdgeProcessor queueEdgeProcessor; + @Autowired private EdgeEventStorageSettings edgeEventStorageSettings; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 6c991444b2..2f9b4c0498 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -536,6 +536,10 @@ public final class EdgeGrpcSession implements Closeable { return ctx.getWidgetTypeProcessor().processWidgetTypeToEdge(edgeEvent, msgType, action); case ADMIN_SETTINGS: return ctx.getAdminSettingsProcessor().processAdminSettingsToEdge(edgeEvent); + case OTA_PACKAGE: + return ctx.getOtaPackageEdgeProcessor().processOtaPackageToEdge(edgeEvent, msgType, action); + case QUEUE: + return ctx.getQueueEdgeProcessor().processQueueToEdge(edgeEvent, msgType, action); default: log.warn("Unsupported edge event type [{}]", edgeEvent); return null; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index a83609fa8f..1df44dd589 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -25,6 +25,8 @@ import org.thingsboard.server.service.edge.rpc.fetch.CustomerUsersEdgeEventFetch import org.thingsboard.server.service.edge.rpc.fetch.DashboardsEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.DeviceProfilesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher; +import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher; +import org.thingsboard.server.service.edge.rpc.fetch.QueuesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.SystemWidgetsBundlesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.TenantAdminUsersEdgeEventFetcher; @@ -41,6 +43,7 @@ public class EdgeSyncCursor { int currentIdx = 0; public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge) { + fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService())); fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig())); fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService())); @@ -53,6 +56,7 @@ public class EdgeSyncCursor { fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService())); + fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService())); } public boolean hasNext() { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java index 77dc121e38..4e12fa3366 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java @@ -20,7 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -48,9 +48,9 @@ public class DeviceProfileMsgConstructor { // builder.setDefaultRuleChainIdMSB(deviceProfile.getDefaultRuleChainId().getId().getMostSignificantBits()) // .setDefaultRuleChainIdLSB(deviceProfile.getDefaultRuleChainId().getId().getLeastSignificantBits()); // } -// if (deviceProfile.getDefaultQueueName() != null) { -// builder.setDefaultQueueName(deviceProfile.getDefaultQueueName()); -// } + if (deviceProfile.getDefaultQueueName() != null) { + builder.setDefaultQueueName(deviceProfile.getDefaultQueueName()); + } if (deviceProfile.getDescription() != null) { builder.setDescription(deviceProfile.getDescription()); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java new file mode 100644 index 0000000000..be38079d1e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/OtaPackageMsgConstructor.java @@ -0,0 +1,77 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.constructor; + +import com.google.protobuf.ByteString; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Component +@TbCoreComponent +public class OtaPackageMsgConstructor { + + public OtaPackageUpdateMsg constructOtaPackageUpdatedMsg(UpdateMsgType msgType, OtaPackage otaPackage) { + OtaPackageUpdateMsg.Builder builder = OtaPackageUpdateMsg.newBuilder() + .setMsgType(msgType) + .setIdMSB(otaPackage.getId().getId().getMostSignificantBits()) + .setIdLSB(otaPackage.getId().getId().getLeastSignificantBits()) + .setDeviceProfileIdMSB(otaPackage.getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(otaPackage.getDeviceProfileId().getId().getLeastSignificantBits()) + .setType(otaPackage.getType().name()) + .setTitle(otaPackage.getTitle()) + .setVersion(otaPackage.getVersion()) + .setTag(otaPackage.getTag()); + + if (otaPackage.getUrl() != null) { + builder.setUrl(otaPackage.getUrl()); + } + if (otaPackage.getAdditionalInfo() != null) { + builder.setAdditionalInfo(JacksonUtil.toString(otaPackage.getAdditionalInfo())); + } + if (otaPackage.getFileName() != null) { + builder.setFileName(otaPackage.getFileName()); + } + if (otaPackage.getContentType() != null) { + builder.setContentType(otaPackage.getContentType()); + } + if (otaPackage.getChecksumAlgorithm() != null) { + builder.setChecksumAlgorithm(otaPackage.getChecksumAlgorithm().name()); + } + if (otaPackage.getChecksum() != null) { + builder.setChecksum(otaPackage.getChecksum()); + } + if (otaPackage.getDataSize() != null) { + builder.setDataSize(otaPackage.getDataSize()); + } + if (otaPackage.getData() != null) { + builder.setData(ByteString.copyFrom(otaPackage.getData().array())); + } + return builder.build(); + } + + public OtaPackageUpdateMsg constructOtaPackageDeleteMsg(OtaPackageId otaPackageId) { + return OtaPackageUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(otaPackageId.getId().getMostSignificantBits()) + .setIdLSB(otaPackageId.getId().getLeastSignificantBits()).build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/QueueMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/QueueMsgConstructor.java new file mode 100644 index 0000000000..85362ff70b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/QueueMsgConstructor.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.service.edge.rpc.constructor; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.QueueId; +import org.thingsboard.server.common.data.queue.ProcessingStrategy; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.queue.SubmitStrategy; +import org.thingsboard.server.gen.edge.v1.ProcessingStrategyProto; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; +import org.thingsboard.server.gen.edge.v1.SubmitStrategyProto; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Component +@TbCoreComponent +public class QueueMsgConstructor { + + public QueueUpdateMsg constructQueueUpdatedMsg(UpdateMsgType msgType, Queue queue) { + QueueUpdateMsg.Builder builder = QueueUpdateMsg.newBuilder() + .setMsgType(msgType) + .setIdMSB(queue.getId().getId().getMostSignificantBits()) + .setIdLSB(queue.getId().getId().getLeastSignificantBits()) + .setTenantIdMSB(queue.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(queue.getTenantId().getId().getLeastSignificantBits()) + .setName(queue.getName()) + .setTopic(queue.getTopic()) + .setPollInterval(queue.getPollInterval()) + .setPartitions(queue.getPartitions()) + .setConsumerPerPartition(queue.isConsumerPerPartition()) + .setPackProcessingTimeout(queue.getPackProcessingTimeout()) + .setSubmitStrategy(createSubmitStrategyProto(queue.getSubmitStrategy())) + .setProcessingStrategy(createProcessingStrategyProto(queue.getProcessingStrategy())); + return builder.build(); + } + + private ProcessingStrategyProto createProcessingStrategyProto(ProcessingStrategy processingStrategy) { + return ProcessingStrategyProto.newBuilder() + .setType(processingStrategy.getType().name()) + .setRetries(processingStrategy.getRetries()) + .setFailurePercentage(processingStrategy.getFailurePercentage()) + .setPauseBetweenRetries(processingStrategy.getPauseBetweenRetries()) + .setMaxPauseBetweenRetries(processingStrategy.getMaxPauseBetweenRetries()) + .build(); + } + + private SubmitStrategyProto createSubmitStrategyProto(SubmitStrategy submitStrategy) { + return SubmitStrategyProto.newBuilder() + .setType(submitStrategy.getType().name()) + .setBatchSize(submitStrategy.getBatchSize()) + .build(); + } + + public QueueUpdateMsg constructQueueDeleteMsg(QueueId queueId) { + return QueueUpdateMsg.newBuilder() + .setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE) + .setIdMSB(queueId.getId().getMostSignificantBits()) + .setIdLSB(queueId.getId().getLeastSignificantBits()).build(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java index 0fc62cfe41..35ac760faa 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java @@ -15,44 +15,26 @@ */ package org.thingsboard.server.service.edge.rpc.constructor; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.rule.NodeConnectionInfo; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.gen.edge.v1.EdgeVersion; -import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto; -import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RuleNodeProto; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.ArrayList; -import java.util.List; -import java.util.NavigableSet; -import java.util.TreeSet; -import java.util.UUID; -import java.util.stream.Collectors; +import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMetadataConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMetadataConstructorFactory; @Component @Slf4j @TbCoreComponent public class RuleChainMsgConstructor { - private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final String RULE_CHAIN_INPUT_NODE = "org.thingsboard.rule.engine.flow.TbRuleChainInputNode"; - private static final String TB_RULE_CHAIN_OUTPUT_NODE = "org.thingsboard.rule.engine.flow.TbRuleChainOutputNode"; - public RuleChainUpdateMsg constructRuleChainUpdatedMsg(RuleChainId edgeRootRuleChainId, UpdateMsgType msgType, RuleChain ruleChain) { RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder() .setMsgType(msgType) @@ -69,231 +51,13 @@ public class RuleChainMsgConstructor { return builder.build(); } - public RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, + public RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(TenantId tenantId, + UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData, EdgeVersion edgeVersion) { - try { - RuleChainMetadataUpdateMsg.Builder builder = RuleChainMetadataUpdateMsg.newBuilder(); - switch (edgeVersion) { - case V_3_3_0: - constructRuleChainMetadataUpdatedMsg_V_3_3_0(builder, ruleChainMetaData); - break; - case V_3_3_3: - default: - constructRuleChainMetadataUpdatedMsg_V_3_3_3(builder, ruleChainMetaData); - break; - } - builder.setMsgType(msgType); - return builder.build(); - } catch (JsonProcessingException ex) { - log.error("Can't construct RuleChainMetadataUpdateMsg", ex); - } - return null; - } - - private void constructRuleChainMetadataUpdatedMsg_V_3_3_3(RuleChainMetadataUpdateMsg.Builder builder, - RuleChainMetaData ruleChainMetaData) throws JsonProcessingException { - builder.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits()) - .setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits()) - .addAllNodes(constructNodes(ruleChainMetaData.getNodes())) - .addAllConnections(constructConnections(ruleChainMetaData.getConnections())) - .addAllRuleChainConnections(constructRuleChainConnections(ruleChainMetaData.getRuleChainConnections(), new TreeSet<>())); - if (ruleChainMetaData.getFirstNodeIndex() != null) { - builder.setFirstNodeIndex(ruleChainMetaData.getFirstNodeIndex()); - } else { - builder.setFirstNodeIndex(-1); - } - } - - private void constructRuleChainMetadataUpdatedMsg_V_3_3_0(RuleChainMetadataUpdateMsg.Builder builder, - RuleChainMetaData ruleChainMetaData) throws JsonProcessingException { - List supportedNodes = filterNodes_V_3_3_0(ruleChainMetaData.getNodes()); - NavigableSet removedNodeIndexes = getRemovedNodeIndexes(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections()); - List connections = filterConnections_V_3_3_0(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections(), removedNodeIndexes); - - List ruleChainConnections = new ArrayList<>(); - if (ruleChainMetaData.getRuleChainConnections() != null) { - ruleChainConnections.addAll(ruleChainMetaData.getRuleChainConnections()); - } - ruleChainConnections.addAll(addRuleChainConnections_V_3_3_0(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections())); - builder.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits()) - .setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits()) - .addAllNodes(constructNodes(supportedNodes)) - .addAllConnections(constructConnections(connections)) - .addAllRuleChainConnections(constructRuleChainConnections(ruleChainConnections, removedNodeIndexes)); - if (ruleChainMetaData.getFirstNodeIndex() != null) { - Integer firstNodeIndex = ruleChainMetaData.getFirstNodeIndex(); - // decrease index because of removed nodes - for (Integer removedIndex : removedNodeIndexes) { - if (firstNodeIndex > removedIndex) { - firstNodeIndex = firstNodeIndex - 1; - } - } - builder.setFirstNodeIndex(firstNodeIndex); - } else { - builder.setFirstNodeIndex(-1); - } - } - - private List filterConnections_V_3_3_0(List nodes, List connections, NavigableSet removedNodeIndexes) { - List result = new ArrayList<>(); - if (connections != null) { - result = connections.stream().filter(conn -> { - for (int i = 0; i < nodes.size(); i++) { - RuleNode node = nodes.get(i); - if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE) - || node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) { - if (conn.getFromIndex() == i || conn.getToIndex() == i) { - return false; - } - } - } - return true; - }).map(conn -> { - NodeConnectionInfo newConn = new NodeConnectionInfo(); - newConn.setFromIndex(conn.getFromIndex()); - newConn.setToIndex(conn.getToIndex()); - newConn.setType(conn.getType()); - return newConn; - }).collect(Collectors.toList()); - } - - // decrease index because of removed nodes - for (Integer removedIndex : removedNodeIndexes) { - for (NodeConnectionInfo newConn : result) { - if (newConn.getToIndex() > removedIndex) { - newConn.setToIndex(newConn.getToIndex() - 1); - } - if (newConn.getFromIndex() > removedIndex) { - newConn.setFromIndex(newConn.getFromIndex() - 1); - } - } - } - - return result; - } - - private NavigableSet getRemovedNodeIndexes(List nodes, List connections) { - TreeSet removedIndexes = new TreeSet<>(); - for (NodeConnectionInfo connection : connections) { - for (int i = 0; i < nodes.size(); i++) { - RuleNode node = nodes.get(i); - if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE) - || node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) { - if (connection.getFromIndex() == i || connection.getToIndex() == i) { - removedIndexes.add(i); - } - } - } - } - return removedIndexes.descendingSet(); - } - - private List constructConnections(List connections) { - List result = new ArrayList<>(); - if (connections != null && !connections.isEmpty()) { - for (NodeConnectionInfo connection : connections) { - result.add(constructConnection(connection)); - } - } - return result; - } - - private NodeConnectionInfoProto constructConnection(NodeConnectionInfo connection) { - return NodeConnectionInfoProto.newBuilder() - .setFromIndex(connection.getFromIndex()) - .setToIndex(connection.getToIndex()) - .setType(connection.getType()) - .build(); - } - - private List filterNodes_V_3_3_0(List nodes) { - List result = new ArrayList<>(); - for (RuleNode node : nodes) { - switch (node.getType()) { - case RULE_CHAIN_INPUT_NODE: - case TB_RULE_CHAIN_OUTPUT_NODE: - log.trace("Skipping not supported rule node {}", node); - break; - default: - result.add(node); - } - } - return result; - } - - private List constructNodes(List nodes) throws JsonProcessingException { - List result = new ArrayList<>(); - if (nodes != null && !nodes.isEmpty()) { - for (RuleNode node : nodes) { - result.add(constructNode(node)); - } - } - return result; - } - - private List addRuleChainConnections_V_3_3_0(List nodes, List connections) throws JsonProcessingException { - List result = new ArrayList<>(); - for (int i = 0; i < nodes.size(); i++) { - RuleNode node = nodes.get(i); - if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE)) { - for (NodeConnectionInfo connection : connections) { - if (connection.getToIndex() == i) { - RuleChainConnectionInfo e = new RuleChainConnectionInfo(); - e.setFromIndex(connection.getFromIndex()); - TbRuleChainInputNodeConfiguration configuration = JacksonUtil.treeToValue(node.getConfiguration(), TbRuleChainInputNodeConfiguration.class); - e.setTargetRuleChainId(new RuleChainId(UUID.fromString(configuration.getRuleChainId()))); - e.setAdditionalInfo(node.getAdditionalInfo()); - e.setType(connection.getType()); - result.add(e); - } - } - } - } - return result; - } - - private List constructRuleChainConnections(List ruleChainConnections, NavigableSet removedNodeIndexes) throws JsonProcessingException { - List result = new ArrayList<>(); - if (ruleChainConnections != null && !ruleChainConnections.isEmpty()) { - for (RuleChainConnectionInfo ruleChainConnectionInfo : ruleChainConnections) { - result.add(constructRuleChainConnection(ruleChainConnectionInfo, removedNodeIndexes)); - } - } - return result; - } - - private RuleChainConnectionInfoProto constructRuleChainConnection(RuleChainConnectionInfo ruleChainConnectionInfo, NavigableSet removedNodeIndexes) throws JsonProcessingException { - int fromIndex = ruleChainConnectionInfo.getFromIndex(); - // decrease index because of removed nodes - for (Integer removedIndex : removedNodeIndexes) { - if (fromIndex > removedIndex) { - fromIndex = fromIndex - 1; - } - } - ObjectNode additionalInfo = (ObjectNode) ruleChainConnectionInfo.getAdditionalInfo(); - if (additionalInfo.get("ruleChainNodeId") == null) { - additionalInfo.put("ruleChainNodeId", "rule-chain-node-UNDEFINED"); - } - return RuleChainConnectionInfoProto.newBuilder() - .setFromIndex(fromIndex) - .setTargetRuleChainIdMSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getMostSignificantBits()) - .setTargetRuleChainIdLSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getLeastSignificantBits()) - .setType(ruleChainConnectionInfo.getType()) - .setAdditionalInfo(objectMapper.writeValueAsString(additionalInfo)) - .build(); - } - - private RuleNodeProto constructNode(RuleNode node) throws JsonProcessingException { - return RuleNodeProto.newBuilder() - .setIdMSB(node.getId().getId().getMostSignificantBits()) - .setIdLSB(node.getId().getId().getLeastSignificantBits()) - .setType(node.getType()) - .setName(node.getName()) - .setDebugMode(node.isDebugMode()) - .setConfiguration(objectMapper.writeValueAsString(node.getConfiguration())) - .setAdditionalInfo(objectMapper.writeValueAsString(node.getAdditionalInfo())) - .build(); + RuleChainMetadataConstructor ruleChainMetadataConstructor + = RuleChainMetadataConstructorFactory.getByEdgeVersion(edgeVersion); + return ruleChainMetadataConstructor.constructRuleChainMetadataUpdatedMsg(tenantId, msgType, ruleChainMetaData); } public RuleChainUpdateMsg constructRuleChainDeleteMsg(RuleChainId ruleChainId) { @@ -302,5 +66,4 @@ public class RuleChainMsgConstructor { .setIdMSB(ruleChainId.getId().getMostSignificantBits()) .setIdLSB(ruleChainId.getId().getLeastSignificantBits()).build(); } - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/AbstractRuleChainMetadataConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/AbstractRuleChainMetadataConstructor.java new file mode 100644 index 0000000000..8fb32926f5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/AbstractRuleChainMetadataConstructor.java @@ -0,0 +1,137 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.constructor.rule; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto; +import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleNodeProto; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; + +@Slf4j +@AllArgsConstructor +public abstract class AbstractRuleChainMetadataConstructor implements RuleChainMetadataConstructor { + + @Override + public RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(TenantId tenantId, + UpdateMsgType msgType, + RuleChainMetaData ruleChainMetaData) { + try { + RuleChainMetadataUpdateMsg.Builder builder = RuleChainMetadataUpdateMsg.newBuilder(); + builder.setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits()) + .setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits()); + constructRuleChainMetadataUpdatedMsg(tenantId, builder, ruleChainMetaData); + builder.setMsgType(msgType); + return builder.build(); + } catch (JsonProcessingException ex) { + log.error("Can't construct RuleChainMetadataUpdateMsg", ex); + } + return null; + } + + protected abstract void constructRuleChainMetadataUpdatedMsg(TenantId tenantId, + RuleChainMetadataUpdateMsg.Builder builder, + RuleChainMetaData ruleChainMetaData) throws JsonProcessingException; + + protected List constructConnections(List connections) { + List result = new ArrayList<>(); + if (connections != null && !connections.isEmpty()) { + for (NodeConnectionInfo connection : connections) { + result.add(constructConnection(connection)); + } + } + return result; + } + + private NodeConnectionInfoProto constructConnection(NodeConnectionInfo connection) { + return NodeConnectionInfoProto.newBuilder() + .setFromIndex(connection.getFromIndex()) + .setToIndex(connection.getToIndex()) + .setType(connection.getType()) + .build(); + } + + protected List constructNodes(List nodes) throws JsonProcessingException { + List result = new ArrayList<>(); + if (nodes != null && !nodes.isEmpty()) { + for (RuleNode node : nodes) { + result.add(constructNode(node)); + } + } + return result; + } + + private RuleNodeProto constructNode(RuleNode node) throws JsonProcessingException { + return RuleNodeProto.newBuilder() + .setIdMSB(node.getId().getId().getMostSignificantBits()) + .setIdLSB(node.getId().getId().getLeastSignificantBits()) + .setType(node.getType()) + .setName(node.getName()) + .setDebugMode(node.isDebugMode()) + .setConfiguration(JacksonUtil.OBJECT_MAPPER.writeValueAsString(node.getConfiguration())) + .setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.writeValueAsString(node.getAdditionalInfo())) + .build(); + } + + protected List constructRuleChainConnections(List ruleChainConnections, + NavigableSet removedNodeIndexes) throws JsonProcessingException { + List result = new ArrayList<>(); + if (ruleChainConnections != null && !ruleChainConnections.isEmpty()) { + for (RuleChainConnectionInfo ruleChainConnectionInfo : ruleChainConnections) { + if (!removedNodeIndexes.isEmpty()) { // 3_3_0 only + int fromIndex = ruleChainConnectionInfo.getFromIndex(); + // decrease index because of removed nodes + for (Integer removedIndex : removedNodeIndexes) { + if (fromIndex > removedIndex) { + fromIndex = fromIndex - 1; + } + } + ruleChainConnectionInfo.setFromIndex(fromIndex); + ObjectNode additionalInfo = (ObjectNode) ruleChainConnectionInfo.getAdditionalInfo(); + if (additionalInfo.get("ruleChainNodeId") == null) { + additionalInfo.put("ruleChainNodeId", "rule-chain-node-UNDEFINED"); + } + } + result.add(constructRuleChainConnection(ruleChainConnectionInfo)); + } + } + return result; + } + + private RuleChainConnectionInfoProto constructRuleChainConnection(RuleChainConnectionInfo ruleChainConnectionInfo) throws JsonProcessingException { + return RuleChainConnectionInfoProto.newBuilder() + .setFromIndex(ruleChainConnectionInfo.getFromIndex()) + .setTargetRuleChainIdMSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getMostSignificantBits()) + .setTargetRuleChainIdLSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getLeastSignificantBits()) + .setType(ruleChainConnectionInfo.getType()) + .setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.writeValueAsString(ruleChainConnectionInfo.getAdditionalInfo())) + .build(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java new file mode 100644 index 0000000000..014023cb60 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructor.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.constructor.rule; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +public interface RuleChainMetadataConstructor { + + RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(TenantId tenantId, + UpdateMsgType msgType, + RuleChainMetaData ruleChainMetaData); +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java new file mode 100644 index 0000000000..5fc686f86a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorFactory.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.constructor.rule; + +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +public final class RuleChainMetadataConstructorFactory { + + public static RuleChainMetadataConstructor getByEdgeVersion(EdgeVersion edgeVersion) { + switch (edgeVersion) { + case V_3_3_0: + return new RuleChainMetadataConstructorV330(); + case V_3_3_3: + case V_3_4_0: + default: + return new RuleChainMetadataConstructorV340(); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java new file mode 100644 index 0000000000..3172ee9277 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV330.java @@ -0,0 +1,165 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.constructor.rule; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; +import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; +import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; + +import java.util.ArrayList; +import java.util.List; +import java.util.NavigableSet; +import java.util.TreeSet; +import java.util.UUID; +import java.util.stream.Collectors; + +@Slf4j +public class RuleChainMetadataConstructorV330 extends AbstractRuleChainMetadataConstructor { + + private static final String RULE_CHAIN_INPUT_NODE = TbRuleChainInputNode.class.getName(); + private static final String TB_RULE_CHAIN_OUTPUT_NODE = TbRuleChainOutputNode.class.getName(); + + @Override + protected void constructRuleChainMetadataUpdatedMsg(TenantId tenantId, + RuleChainMetadataUpdateMsg.Builder builder, + RuleChainMetaData ruleChainMetaData) throws JsonProcessingException { + List supportedNodes = filterNodes(ruleChainMetaData.getNodes()); + + NavigableSet removedNodeIndexes = getRemovedNodeIndexes(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections()); + List connections = filterConnections(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections(), removedNodeIndexes); + + List ruleChainConnections = new ArrayList<>(); + if (ruleChainMetaData.getRuleChainConnections() != null) { + ruleChainConnections.addAll(ruleChainMetaData.getRuleChainConnections()); + } + ruleChainConnections.addAll(addRuleChainConnections(ruleChainMetaData.getNodes(), ruleChainMetaData.getConnections())); + builder.addAllNodes(constructNodes(supportedNodes)) + .addAllConnections(constructConnections(connections)) + .addAllRuleChainConnections(constructRuleChainConnections(ruleChainConnections, removedNodeIndexes)); + if (ruleChainMetaData.getFirstNodeIndex() != null) { + Integer firstNodeIndex = ruleChainMetaData.getFirstNodeIndex(); + // decrease index because of removed nodes + for (Integer removedIndex : removedNodeIndexes) { + if (firstNodeIndex > removedIndex) { + firstNodeIndex = firstNodeIndex - 1; + } + } + builder.setFirstNodeIndex(firstNodeIndex); + } else { + builder.setFirstNodeIndex(-1); + } + } + + private NavigableSet getRemovedNodeIndexes(List nodes, List connections) { + TreeSet removedIndexes = new TreeSet<>(); + for (NodeConnectionInfo connection : connections) { + for (int i = 0; i < nodes.size(); i++) { + RuleNode node = nodes.get(i); + if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE) + || node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) { + if (connection.getFromIndex() == i || connection.getToIndex() == i) { + removedIndexes.add(i); + } + } + } + } + return removedIndexes.descendingSet(); + } + + private List filterConnections(List nodes, + List connections, + NavigableSet removedNodeIndexes) { + List result = new ArrayList<>(); + if (connections != null) { + result = connections.stream().filter(conn -> { + for (int i = 0; i < nodes.size(); i++) { + RuleNode node = nodes.get(i); + if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE) + || node.getType().equalsIgnoreCase(TB_RULE_CHAIN_OUTPUT_NODE)) { + if (conn.getFromIndex() == i || conn.getToIndex() == i) { + return false; + } + } + } + return true; + }).map(conn -> { + NodeConnectionInfo newConn = new NodeConnectionInfo(); + newConn.setFromIndex(conn.getFromIndex()); + newConn.setToIndex(conn.getToIndex()); + newConn.setType(conn.getType()); + return newConn; + }).collect(Collectors.toList()); + } + + // decrease index because of removed nodes + for (Integer removedIndex : removedNodeIndexes) { + for (NodeConnectionInfo newConn : result) { + if (newConn.getToIndex() > removedIndex) { + newConn.setToIndex(newConn.getToIndex() - 1); + } + if (newConn.getFromIndex() > removedIndex) { + newConn.setFromIndex(newConn.getFromIndex() - 1); + } + } + } + + return result; + } + + private List filterNodes(List nodes) { + List result = new ArrayList<>(); + for (RuleNode node : nodes) { + if (RULE_CHAIN_INPUT_NODE.equals(node.getType()) + || TB_RULE_CHAIN_OUTPUT_NODE.equals(node.getType())) { + log.trace("Skipping not supported rule node {}", node); + } else { + result.add(node); + } + } + return result; + } + + private List addRuleChainConnections(List nodes, List connections) { + List result = new ArrayList<>(); + for (int i = 0; i < nodes.size(); i++) { + RuleNode node = nodes.get(i); + if (node.getType().equalsIgnoreCase(RULE_CHAIN_INPUT_NODE)) { + for (NodeConnectionInfo connection : connections) { + if (connection.getToIndex() == i) { + RuleChainConnectionInfo e = new RuleChainConnectionInfo(); + e.setFromIndex(connection.getFromIndex()); + TbRuleChainInputNodeConfiguration configuration = JacksonUtil.treeToValue(node.getConfiguration(), TbRuleChainInputNodeConfiguration.class); + e.setTargetRuleChainId(new RuleChainId(UUID.fromString(configuration.getRuleChainId()))); + e.setAdditionalInfo(node.getAdditionalInfo()); + e.setType(connection.getType()); + result.add(e); + } + } + } + } + return result; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java new file mode 100644 index 0000000000..9acb2ae9f5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/rule/RuleChainMetadataConstructorV340.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.constructor.rule; + +import com.fasterxml.jackson.core.JsonProcessingException; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; + +import java.util.TreeSet; + +@Slf4j +public class RuleChainMetadataConstructorV340 extends AbstractRuleChainMetadataConstructor { + + @Override + protected void constructRuleChainMetadataUpdatedMsg(TenantId tenantId, + RuleChainMetadataUpdateMsg.Builder builder, + RuleChainMetaData ruleChainMetaData) throws JsonProcessingException { + builder.addAllNodes(constructNodes(ruleChainMetaData.getNodes())) + .addAllConnections(constructConnections(ruleChainMetaData.getConnections())) + .addAllRuleChainConnections(constructRuleChainConnections(ruleChainMetaData.getRuleChainConnections(), new TreeSet<>())); + if (ruleChainMetaData.getFirstNodeIndex() != null) { + builder.setFirstNodeIndex(ruleChainMetaData.getFirstNodeIndex()); + } else { + builder.setFirstNodeIndex(-1); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index 0f4ecf7e30..ee10c7859b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -83,7 +83,7 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailSettings))); - AdminSettings tenantMailSettings = convertToTenantAdminSettings(systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue()); + AdminSettings tenantMailSettings = convertToTenantAdminSettings(tenantId, systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue()); result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailSettings))); @@ -91,7 +91,7 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailTemplates))); - AdminSettings tenantMailTemplates = convertToTenantAdminSettings(systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue()); + AdminSettings tenantMailTemplates = convertToTenantAdminSettings(tenantId, systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue()); result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailTemplates))); @@ -151,8 +151,9 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { } } - private AdminSettings convertToTenantAdminSettings(String key, ObjectNode jsonValue) { + private AdminSettings convertToTenantAdminSettings(TenantId tenantId, String key, ObjectNode jsonValue) { AdminSettings tenantMailSettings = new AdminSettings(); + tenantMailSettings.setTenantId(tenantId); jsonValue.put("useSystemMailSettings", true); tenantMailSettings.setJsonValue(jsonValue); tenantMailSettings.setKey(key); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java new file mode 100644 index 0000000000..14b8bac3f4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/OtaPackagesEdgeEventFetcher.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.fetch; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.ota.OtaPackageService; + +@AllArgsConstructor +@Slf4j +public class OtaPackagesEdgeEventFetcher extends BasePageableEdgeEventFetcher { + + private final OtaPackageService otaPackageService; + + @Override + PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) { + return otaPackageService.findTenantOtaPackagesByTenantId(tenantId, pageLink); + } + + @Override + EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, OtaPackageInfo otaPackageInfo) { + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OTA_PACKAGE, + EdgeEventActionType.ADDED, otaPackageInfo.getId(), null); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/QueuesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/QueuesEdgeEventFetcher.java new file mode 100644 index 0000000000..a47dfa50a1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/QueuesEdgeEventFetcher.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.fetch; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.dao.queue.QueueService; + +@AllArgsConstructor +@Slf4j +public class QueuesEdgeEventFetcher extends BasePageableEdgeEventFetcher { + + private final QueueService queueService; + + @Override + PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) { + return queueService.findQueuesByTenantId(tenantId, pageLink); + } + + @Override + EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, Queue queue) { + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.QUEUE, + EdgeEventActionType.ADDED, queue.getId(), null); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java index 54a5e202ab..9983e4b0b2 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantWidgetsBundlesEdgeEventFetcher.java @@ -32,6 +32,6 @@ public class TenantWidgetsBundlesEdgeEventFetcher extends BaseWidgetsBundlesEdge } @Override protected PageData findWidgetsBundles(TenantId tenantId, PageLink pageLink) { - return widgetsBundleService.findAllTenantWidgetsBundlesByTenantIdAndPageLink(tenantId, pageLink); + return widgetsBundleService.findTenantWidgetsBundlesByTenantId(tenantId, pageLink); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 09bef32af9..9d6199bdcd 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EdgeUtils; @@ -46,12 +47,17 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.service.edge.rpc.constructor.AdminSettingsMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.AlarmMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.AssetMsgConstructor; @@ -61,6 +67,8 @@ import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.DeviceProfileMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.EntityViewMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.OtaPackageMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.QueueMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.RelationMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.RuleChainMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor; @@ -101,6 +109,9 @@ public abstract class BaseEdgeProcessor { @Autowired protected EntityViewService entityViewService; + @Autowired + protected TenantService tenantService; + @Autowired protected EdgeService edgeService; @@ -137,6 +148,19 @@ public abstract class BaseEdgeProcessor { @Autowired protected WidgetTypeService widgetTypeService; + @Autowired + protected OtaPackageService otaPackageService; + + @Autowired + protected QueueService queueService; + + @Autowired + protected PartitionService partitionService; + + @Autowired + @Lazy + protected TbQueueProducerProvider producerProvider; + @Autowired protected DataValidator deviceValidator; @@ -182,6 +206,12 @@ public abstract class BaseEdgeProcessor { @Autowired protected AdminSettingsMsgConstructor adminSettingsMsgConstructor; + @Autowired + protected OtaPackageMsgConstructor otaPackageMsgConstructor; + + @Autowired + protected QueueMsgConstructor queueMsgConstructor; + @Autowired protected DbCallbackExecutorService dbCallbackExecutorService; @@ -212,6 +242,24 @@ public abstract class BaseEdgeProcessor { } protected ListenableFuture processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { + List> futures = new ArrayList<>(); + if (TenantId.SYS_TENANT_ID.equals(tenantId)) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData tenantsIds; + do { + tenantsIds = tenantService.findTenantsIds(pageLink); + for (TenantId tenantId1 : tenantsIds.getData()) { + futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId)); + } + pageLink = pageLink.nextPageLink(); + } while (tenantsIds.hasNext()); + } else { + futures = processActionForAllEdgesByTenantId(tenantId, type, actionType, entityId); + } + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + } + + private List> processActionForAllEdgesByTenantId(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageData pageData; List> futures = new ArrayList<>(); @@ -226,6 +274,6 @@ public abstract class BaseEdgeProcessor { } } } while (pageData != null && pageData.hasNext()); - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + return futures; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java new file mode 100644 index 0000000000..0ccce4c346 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Component +@Slf4j +@TbCoreComponent +public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { + + public DownlinkMsg processOtaPackageToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId()); + DownlinkMsg downlinkMsg = null; + switch (action) { + case ADDED: + case UPDATED: + OtaPackage otaPackage = otaPackageService.findOtaPackageById(edgeEvent.getTenantId(), otaPackageId); + if (otaPackage != null) { + OtaPackageUpdateMsg otaPackageUpdateMsg = + otaPackageMsgConstructor.constructOtaPackageUpdatedMsg(msgType, otaPackage); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addOtaPackageUpdateMsg(otaPackageUpdateMsg) + .build(); + } + break; + case DELETED: + OtaPackageUpdateMsg otaPackageUpdateMsg = + otaPackageMsgConstructor.constructOtaPackageDeleteMsg(otaPackageId); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addOtaPackageUpdateMsg(otaPackageUpdateMsg) + .build(); + break; + } + return downlinkMsg; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java new file mode 100644 index 0000000000..2993c651da --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.processor; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.QueueId; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Component +@Slf4j +@TbCoreComponent +public class QueueEdgeProcessor extends BaseEdgeProcessor { + + public DownlinkMsg processQueueToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + QueueId queueId = new QueueId(edgeEvent.getEntityId()); + DownlinkMsg downlinkMsg = null; + switch (action) { + case ADDED: + case UPDATED: + Queue queue = queueService.findQueueById(edgeEvent.getTenantId(), queueId); + if (queue != null) { + QueueUpdateMsg queueUpdateMsg = + queueMsgConstructor.constructQueueUpdatedMsg(msgType, queue); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addQueueUpdateMsg(queueUpdateMsg) + .build(); + } + break; + case DELETED: + QueueUpdateMsg queueDeleteMsg = + queueMsgConstructor.constructQueueDeleteMsg(queueId); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addQueueUpdateMsg(queueDeleteMsg) + .build(); + break; + } + return downlinkMsg; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java index ebbac4cbed..5620dc9a7c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java @@ -71,7 +71,7 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { if (ruleChain != null) { RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = - ruleChainMsgConstructor.constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData, edgeVersion); + ruleChainMsgConstructor.constructRuleChainMetadataUpdatedMsg(edgeEvent.getTenantId(), msgType, ruleChainMetaData, edgeVersion); if (ruleChainMetadataUpdateMsg != null) { downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java index 769d988507..ad6446cb95 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java @@ -52,6 +52,8 @@ import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.util.JsonUtils; @@ -61,9 +63,12 @@ import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; import javax.annotation.Nullable; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -77,14 +82,19 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { private final Gson gson = new Gson(); + private TbQueueProducer> tbCoreMsgProducer; + + @PostConstruct + public void init() { + tbCoreMsgProducer = producerProvider.getTbCoreMsgProducer(); + } + public List> processTelemetryFromEdge(TenantId tenantId, CustomerId customerId, EntityDataProto entityData) { log.trace("[{}] onTelemetryUpdate [{}]", tenantId, entityData); List> result = new ArrayList<>(); EntityId entityId = constructEntityId(entityData); if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg()) && entityId != null) { - // @voba - in terms of performance we should not fetch device from DB by id - // TbMsgMetaData metaData = constructBaseMsgMetadata(tenantId, entityId); - TbMsgMetaData metaData = new TbMsgMetaData(); + TbMsgMetaData metaData = constructBaseMsgMetadata(tenantId, entityId); metaData.putValue(DataConstants.MSG_SOURCE_KEY, DataConstants.EDGE_MSG_SOURCE); if (entityData.hasPostAttributesMsg()) { result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); @@ -96,6 +106,20 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { if (entityData.hasPostTelemetryMsg()) { result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); } + if (EntityType.DEVICE.equals(entityId.getEntityType())) { + DeviceId deviceId = new DeviceId(entityId.getId()); + + TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setLastActivityTime(System.currentTimeMillis()).build(); + + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); + tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(), + TransportProtos.ToCoreMsg.newBuilder().setDeviceActivityMsg(deviceActivityMsg).build()), null); + } } if (entityData.hasAttributeDeleteMsg()) { result.add(processAttributeDeleteMsg(tenantId, entityId, entityData.getAttributeDeleteMsg(), entityData.getEntityType())); @@ -134,21 +158,21 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { return metaData; } - private Pair getDefaultQueueNameAndRuleChainId(TenantId tenantId, EntityId entityId) { + private Pair getDefaultQueueNameAndRuleChainId(TenantId tenantId, EntityId entityId) { if (EntityType.DEVICE.equals(entityId.getEntityType())) { DeviceProfile deviceProfile = deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())); RuleChainId ruleChainId; - QueueId queueId; + String queueName; if (deviceProfile == null) { log.warn("[{}] Device profile is null!", entityId); ruleChainId = null; - queueId = null; + queueName = null; } else { ruleChainId = deviceProfile.getDefaultRuleChainId(); - queueId = deviceProfile.getDefaultQueueId(); + queueName = deviceProfile.getDefaultQueueName(); } - return new ImmutablePair<>(queueId, ruleChainId); + return new ImmutablePair<>(queueName, ruleChainId); } else { return new ImmutablePair<>(null, null); } @@ -159,10 +183,8 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); metaData.putValue("ts", tsKv.getTs() + ""); - Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - QueueId queueId = defaultQueueAndRuleChain.getKey(); - RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); - TbMsg tbMsg = TbMsg.newMsg(queueId, SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), ruleChainId, null); + var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -182,10 +204,8 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { private ListenableFuture processPostAttributes(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); - Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - QueueId queueId = defaultQueueAndRuleChain.getKey(); - RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); - TbMsg tbMsg = TbMsg.newMsg(queueId, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), ruleChainId, null); + var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -209,10 +229,8 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(@Nullable List keys) { - Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); - QueueId queueId = defaultQueueAndRuleChain.getKey(); - RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); - TbMsg tbMsg = TbMsg.newMsg(queueId, DataConstants.ATTRIBUTES_UPDATED, entityId, customerId, metaData, gson.toJson(json), ruleChainId, null); + var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), DataConstants.ATTRIBUTES_UPDATED, entityId, customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java index 9d87cabb2c..e66d7d5595 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java @@ -25,6 +25,7 @@ import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; @@ -60,7 +61,6 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeEventService; -import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -72,7 +72,10 @@ import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.state.DefaultDeviceStateService; import java.util.ArrayList; import java.util.HashMap; @@ -81,6 +84,7 @@ import java.util.Map; import java.util.UUID; @Service +@TbCoreComponent @Slf4j public class DefaultEdgeRequestsService implements EdgeRequestsService { @@ -100,8 +104,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { @Autowired private DeviceService deviceService; + @Lazy @Autowired - private EntityViewService entityViewService; + private TbEntityViewService entityViewService; @Autowired private DeviceProfileService deviceProfileService; @@ -160,6 +165,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { Map entityData = new HashMap<>(); ObjectNode attributes = mapper.createObjectNode(); for (AttributeKvEntry attr : ssAttributes) { + if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())) { + continue; + } if (attr.getDataType() == DataType.BOOLEAN && attr.getBooleanValue().isPresent()) { attributes.put(attr.getKey(), attr.getBooleanValue().get()); } else if (attr.getDataType() == DataType.DOUBLE && attr.getDoubleValue().isPresent()) { @@ -378,7 +386,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { } List> futures = new ArrayList<>(); for (EntityView entityView : entityViews) { - ListenableFuture future = relationService.checkRelation(tenantId, edge.getId(), entityView.getId(), + ListenableFuture future = relationService.checkRelationAsync(tenantId, edge.getId(), entityView.getId(), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE); futures.add(Futures.transformAsync(future, result -> { if (Boolean.TRUE.equals(result)) { @@ -413,11 +421,11 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { } private ListenableFuture saveEdgeEvent(TenantId tenantId, - EdgeId edgeId, - EdgeEventType type, - EdgeEventActionType action, - EntityId entityId, - JsonNode body) { + EdgeId edgeId, + EdgeEventType type, + EdgeEventActionType action, + EntityId entityId, + JsonNode body) { log.trace("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]", tenantId, edgeId, type, action, entityId, body); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java index 395ebdeaac..0e66e365da 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java @@ -23,15 +23,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -40,42 +37,17 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.dao.asset.AssetService; -import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; -import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.device.ClaimDevicesService; -import org.thingsboard.server.dao.device.DeviceCredentialsService; -import org.thingsboard.server.dao.device.DeviceProfileService; -import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeService; -import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.ota.OtaPackageService; -import org.thingsboard.server.dao.queue.QueueService; -import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.rule.RuleChainService; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.dao.user.UserService; -import org.thingsboard.server.dao.widget.WidgetsBundleService; -import org.thingsboard.server.service.action.EntityActionService; -import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; -import org.thingsboard.server.service.install.InstallScripts; -import org.thingsboard.server.service.ota.OtaPackageStateService; -import org.thingsboard.server.service.resource.TbResourceService; -import org.thingsboard.server.service.rule.TbRuleChainService; -import org.thingsboard.server.service.security.permission.AccessControlService; -import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; - -import javax.mail.MessagingException; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; + import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; @Slf4j @@ -92,64 +64,18 @@ public abstract class AbstractTbEntityService { @Autowired protected DbCallbackExecutorService dbExecutor; - @Autowired + @Autowired(required = false) protected TbNotificationEntityService notificationEntityService; @Autowired(required = false) protected EdgeService edgeService; @Autowired protected AlarmService alarmService; @Autowired - protected EntityActionService entityActionService; - @Autowired - protected DeviceService deviceService; - @Autowired - protected AssetService assetService; - @Autowired - protected DeviceCredentialsService deviceCredentialsService; - @Autowired - protected TenantService tenantService; - @Autowired protected CustomerService customerService; @Autowired - protected ClaimDevicesService claimDevicesService; - @Autowired - protected TbTenantProfileCache tenantProfileCache; - @Autowired - protected RuleChainService ruleChainService; - @Autowired - protected TbRuleChainService tbRuleChainService; - @Autowired - protected EdgeNotificationService edgeNotificationService; - @Autowired - protected QueueService queueService; - @Autowired - protected DashboardService dashboardService; - @Autowired - protected EntityViewService entityViewService; - @Autowired - protected TelemetrySubscriptionService tsSubService; - @Autowired - protected AttributesService attributesService; - @Autowired - protected AccessControlService accessControlService; - @Autowired - protected DeviceProfileService deviceProfileService; - @Autowired protected TbClusterService tbClusterService; - @Autowired - protected OtaPackageStateService otaPackageStateService; - @Autowired - protected RelationService relationService; - @Autowired - protected OtaPackageService otaPackageService; - @Autowired - protected InstallScripts installScripts; - @Autowired - protected UserService userService; - @Autowired - protected TbResourceService resourceService; - @Autowired - protected WidgetsBundleService widgetsBundleService; + @Autowired(required = false) + private EntitiesVersionControlService vcService; protected ListenableFuture removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) { ListenableFuture> alarmsFuture = @@ -164,15 +90,6 @@ public abstract class AbstractTbEntityService { }, dbExecutor); } - protected void logEntityAction(User user, TenantId tenantId, I entityId, E entity, CustomerId customerId, - ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException { - if (user != null) { - entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); - } else if (e == null) { - entityActionService.pushEntityActionToRuleEngine(entityId, entity, tenantId, customerId, actionType, null, additionalInfo); - } - } - protected T checkNotNull(T reference) throws ThingsboardException { return checkNotNull(reference, "Requested item wasn't found!"); } @@ -196,37 +113,6 @@ public abstract class AbstractTbEntityService { } } - protected ThingsboardException handleException(Exception exception) { - return handleException(exception, true); - } - - protected ThingsboardException handleException(Exception exception, boolean logException) { - if (logException && logControllerErrorStackTrace) { - log.error("Error [{}]", exception.getMessage(), exception); - } - - String cause = ""; - if (exception.getCause() != null) { - cause = exception.getCause().getClass().getCanonicalName(); - } - - if (exception instanceof ThingsboardException) { - return (ThingsboardException) exception; - } else if (exception instanceof IllegalArgumentException || exception instanceof IncorrectParameterException - || exception instanceof DataValidationException || cause.contains("IncorrectParameterException")) { - return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } else if (exception instanceof MessagingException) { - return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); - } else { - return new ThingsboardException(exception.getMessage(), exception, ThingsboardErrorCode.GENERAL); - } - } - - @SuppressWarnings("unchecked") - protected I emptyId(EntityType entityType) { - return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); - } - protected List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { if (!edgesEnabled) { return null; @@ -242,4 +128,26 @@ public abstract class AbstractTbEntityService { } return result; } + + protected I emptyId(EntityType entityType) { + return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); + } + + protected ListenableFuture autoCommit(User user, EntityId entityId) throws Exception { + if (vcService != null) { + return vcService.autoCommit(user, entityId); + } else { + // We do not support auto-commit for rule engine + return Futures.immediateFailedFuture(new RuntimeException("Operation not supported!")); + } + } + + protected ListenableFuture autoCommit(User user, EntityType entityType, List entityIds) throws Exception { + if (vcService != null) { + return vcService.autoCommit(user, entityType, entityIds); + } else { + // We do not support auto-commit for rule engine + return Futures.immediateFailedFuture(new RuntimeException("Operation not supported!")); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index c7e4f2cd0b..187d8deabd 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -15,11 +15,10 @@ */ package org.thingsboard.server.service.entitiy; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DataConstants; @@ -27,6 +26,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; @@ -46,52 +46,74 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; @Slf4j @Service -@TbCoreComponent @RequiredArgsConstructor public class DefaultTbNotificationEntityService implements TbNotificationEntityService { - private static final ObjectMapper json = new ObjectMapper(); private final EntityActionService entityActionService; private final TbClusterService tbClusterService; private final GatewayNotificationsService gatewayNotificationsService; @Override - public void notifyEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, - ActionType actionType, SecurityUser user, Exception e, - Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, e, additionalInfo); + public void logEntityAction(TenantId tenantId, I entityId, ActionType actionType, + User user, Exception e, Object... additionalInfo) { + logEntityAction(tenantId, entityId, null, null, actionType, user, e, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, + ActionType actionType, User user, Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, null, actionType, user, null, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, + ActionType actionType, User user, Exception e, + Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, null, actionType, user, e, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, User user, Object... additionalInfo) { + logEntityAction(tenantId, entityId, entity, customerId, actionType, user, null, additionalInfo); + } + + @Override + public void logEntityAction(TenantId tenantId, I entityId, E entity, + CustomerId customerId, ActionType actionType, + User user, Exception e, Object... additionalInfo) { + if (user != null) { + entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); + } else if (e == null) { + entityActionService.pushEntityActionToRuleEngine(entityId, entity, tenantId, customerId, actionType, null, additionalInfo); + } } @Override public void notifyDeleteEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, ActionType actionType, List relatedEdgeIds, - SecurityUser user, Object... additionalInfo) { + User user, Object... additionalInfo) { logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); sendDeleteNotificationMsg(tenantId, entityId, entity, relatedEdgeIds); } - public void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId, - CustomerId customerId, - List relatedEdgeIds, - SecurityUser user, - String body, Object... additionalInfo) { + @Override + public void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId, CustomerId customerId, + List relatedEdgeIds, User user, String body, Object... additionalInfo) { logEntityAction(tenantId, originatorId, alarm, customerId, ActionType.DELETED, user, additionalInfo); sendAlarmDeleteNotificationMsg(tenantId, alarm, relatedEdgeIds, body); } @Override - public void notifyDeleteRuleChain(TenantId tenantId, RuleChain ruleChain, - List relatedEdgeIds, SecurityUser user) { + public void notifyDeleteRuleChain(TenantId tenantId, RuleChain ruleChain, List relatedEdgeIds, User user) { RuleChainId ruleChainId = ruleChain.getId(); logEntityAction(tenantId, ruleChainId, ruleChain, null, ActionType.DELETED, user, null, ruleChainId.toString()); if (RuleChainType.EDGE.equals(ruleChain.getType())) { @@ -102,19 +124,18 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS @Override public void notifySendMsgToEdgeService(TenantId tenantId, I entityId, EdgeEventActionType edgeEventActionType) { sendEntityNotificationMsg(tenantId, entityId, edgeEventActionType); - } + } @Override public void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, CustomerId customerId, E entity, ActionType actionType, - EdgeEventActionType edgeActionType, - SecurityUser user, boolean sendToEdge, + User user, boolean sendToEdge, Object... additionalInfo) { logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); if (sendToEdge) { - sendEntityAssignToCustomerNotificationMsg(tenantId, entityId, customerId, edgeActionType); + sendEntityAssignToCustomerNotificationMsg(tenantId, entityId, customerId, edgeTypeByActionType(actionType)); } } @@ -122,13 +143,13 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS public void notifyAssignOrUnassignEntityToEdge(TenantId tenantId, I entityId, CustomerId customerId, EdgeId edgeId, E entity, ActionType actionType, - SecurityUser user, Object... additionalInfo) { + User user, Object... additionalInfo) { logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); sendEntityAssignToEdgeNotificationMsg(tenantId, edgeId, entityId, edgeTypeByActionType(actionType)); } @Override - public void notifyCreateOruUpdateTenant(Tenant tenant, ComponentLifecycleEvent event) { + public void notifyCreateOrUpdateTenant(Tenant tenant, ComponentLifecycleEvent event) { tbClusterService.onTenantChange(tenant, null); tbClusterService.broadcastEntityStateChangeEvent(tenant.getId(), tenant.getId(), event); } @@ -142,14 +163,14 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS @Override public void notifyCreateOrUpdateDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, Device oldDevice, ActionType actionType, - SecurityUser user, Object... additionalInfo) { + User user, Object... additionalInfo) { tbClusterService.onDeviceUpdated(device, oldDevice); logEntityAction(tenantId, deviceId, device, customerId, actionType, user, additionalInfo); } @Override public void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - List relatedEdgeIds, SecurityUser user, Object... additionalInfo) { + List relatedEdgeIds, User user, Object... additionalInfo) { gatewayNotificationsService.onDeviceDeleted(device); tbClusterService.onDeviceDeleted(device, null); @@ -158,7 +179,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS @Override public void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - DeviceCredentials deviceCredentials, SecurityUser user) { + DeviceCredentials deviceCredentials, User user) { tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(tenantId, deviceCredentials.getDeviceId(), deviceCredentials), null); sendEntityNotificationMsg(tenantId, deviceId, EdgeEventActionType.CREDENTIALS_UPDATED); logEntityAction(tenantId, deviceId, device, customerId, ActionType.CREDENTIALS_UPDATED, user, deviceCredentials); @@ -166,13 +187,15 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS @Override public void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId, - Device device, Tenant tenant, SecurityUser user, Object... additionalInfo) { + Device device, Tenant tenant, User user, Object... additionalInfo) { logEntityAction(tenantId, deviceId, device, customerId, ActionType.ASSIGNED_TO_TENANT, user, additionalInfo); pushAssignedFromNotification(tenant, newTenantId, device); } @Override - public void notifyCreateOrUpdateEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, ActionType actionType, SecurityUser user, Object... additionalInfo) { + public void notifyCreateOrUpdateEntity(TenantId tenantId, I entityId, E entity, + CustomerId customerId, ActionType actionType, + User user, Object... additionalInfo) { logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); if (actionType == ActionType.UPDATED) { sendEntityNotificationMsg(tenantId, entityId, EdgeEventActionType.UPDATED); @@ -180,8 +203,8 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS } @Override - public void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, - SecurityUser user, Object... additionalInfo) { + public void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, + ActionType actionType, User user, Object... additionalInfo) { ComponentLifecycleEvent lifecycleEvent; EdgeEventActionType edgeEventActionType = null; switch (actionType) { @@ -216,69 +239,50 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS } @Override - public void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, SecurityUser user, Object... additionalInfo) { + public void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo) { logEntityAction(alarm.getTenantId(), alarm.getOriginator(), alarm, alarm.getCustomerId(), actionType, user, additionalInfo); sendEntityNotificationMsg(alarm.getTenantId(), alarm.getId(), edgeTypeByActionType(actionType)); } @Override public void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId, - I entityId, E entity, SecurityUser user, + I entityId, E entity, User user, ActionType actionType, boolean sendNotifyMsgToEdge, Exception e, Object... additionalInfo) { - notifyEntity(tenantId, entityId, entity, customerId, actionType, user, e, additionalInfo); + logEntityAction(tenantId, entityId, entity, customerId, actionType, user, e, additionalInfo); if (sendNotifyMsgToEdge) { sendEntityNotificationMsg(tenantId, entityId, edgeTypeByActionType(actionType)); } } @Override - public void notifyCreateOrUpdateOrDeleteRelation(TenantId tenantId, CustomerId customerId, - EntityRelation relation, SecurityUser user, - ActionType actionType, Exception e, - Object... additionalInfo) { - notifyEntity(tenantId, relation.getFrom(), null, customerId, actionType, user, e, additionalInfo); - notifyEntity(tenantId, relation.getTo(), null, customerId, actionType, user, e, additionalInfo); - if (e == null) { - try { - if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && - !relation.getTo().getEntityType().equals(EntityType.EDGE)) { - sendNotificationMsgToEdgeService(tenantId, null, null, json.writeValueAsString(relation), - EdgeEventType.RELATION, edgeTypeByActionType(actionType)); - } - } catch (Exception e1) { - log.warn("Failed to push relation to core: {}", relation, e1); + public void notifyRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, + ActionType actionType, Object... additionalInfo) { + logEntityAction(tenantId, relation.getFrom(), null, customerId, actionType, user, additionalInfo); + logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, additionalInfo); + try { + if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && !relation.getTo().getEntityType().equals(EntityType.EDGE)) { + sendNotificationMsgToEdge(tenantId, null, null, JacksonUtil.toString(relation), + EdgeEventType.RELATION, edgeTypeByActionType(actionType)); } - } - } - - private void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, - ActionType actionType, SecurityUser user, Object... additionalInfo) { - logEntityAction(tenantId, entityId, entity, customerId, actionType, user, null, additionalInfo); - } - - private void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, - ActionType actionType, SecurityUser user, Exception e, Object... additionalInfo) { - if (user != null) { - entityActionService.logEntityAction(user, entityId, entity, customerId, actionType, e, additionalInfo); - } else if (e == null) { - entityActionService.pushEntityActionToRuleEngine(entityId, entity, tenantId, customerId, actionType, null, additionalInfo); + } catch (Exception e) { + log.warn("Failed to push relation to core: {}", relation, e); } } private void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdgeService(tenantId, null, entityId, null, null, action); + sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); } private void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) { try { - sendNotificationMsgToEdgeService(tenantId, null, entityId, json.writeValueAsString(customerId), null, action); + sendNotificationMsgToEdge(tenantId, null, entityId, JacksonUtil.toString(customerId), null, action); } catch (Exception e) { log.warn("Failed to push assign/unassign to/from customer to core: {}", customerId, e); } } - protected void sendAlarmDeleteNotificationMsg(TenantId tenantId, Alarm alarm, List edgeIds, String body) { + private void sendAlarmDeleteNotificationMsg(TenantId tenantId, Alarm alarm, List edgeIds, String body) { try { sendDeleteNotificationMsg(tenantId, alarm.getId(), edgeIds, body); } catch (Exception e) { @@ -286,8 +290,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS } } - protected void sendDeleteNotificationMsg(TenantId tenantId, I entityId, E entity, - List edgeIds) { + private void sendDeleteNotificationMsg(TenantId tenantId, I entityId, E entity, List edgeIds) { try { sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null); } catch (Exception e) { @@ -298,23 +301,25 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS private void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, String body) { if (edgeIds != null && !edgeIds.isEmpty()) { for (EdgeId edgeId : edgeIds) { - sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, null, EdgeEventActionType.DELETED); + sendNotificationMsgToEdge(tenantId, edgeId, entityId, body, null, EdgeEventActionType.DELETED); } } } private void sendEntityAssignToEdgeNotificationMsg(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, null, action); + sendNotificationMsgToEdge(tenantId, edgeId, entityId, null, null, action); } - private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { - tbClusterService.sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, body, type, action); + private void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, + EdgeEventType type, EdgeEventActionType action) { + tbClusterService.sendNotificationMsgToEdge(tenantId, edgeId, entityId, body, type, action); } private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { - String data = entityToStr(assignedDevice); + String data = JacksonUtil.toString(JacksonUtil.valueToTree(assignedDevice)); if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), + assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); } } @@ -326,15 +331,6 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS return metaData; } - private String entityToStr(E entity) { - try { - return json.writeValueAsString(json.valueToTree(entity)); - } catch (JsonProcessingException e) { - log.warn("[{}] Failed to convert entity to string!", entity, e); - } - return null; - } - public static EdgeEventActionType edgeTypeByActionType(ActionType actionType) { switch (actionType) { case ADDED: @@ -351,6 +347,10 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS return EdgeEventActionType.RELATION_ADD_OR_UPDATE; case RELATION_DELETED: return EdgeEventActionType.RELATION_DELETED; + case ASSIGNED_TO_CUSTOMER: + return EdgeEventActionType.ASSIGNED_TO_CUSTOMER; + case UNASSIGNED_FROM_CUSTOMER: + return EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER; case ASSIGNED_TO_EDGE: return EdgeEventActionType.ASSIGNED_TO_EDGE; case UNASSIGNED_FROM_EDGE: diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java index ce22e8d787..7c265a4fa3 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/SimpleTbEntityService.java @@ -15,13 +15,16 @@ */ package org.thingsboard.server.service.entitiy; -import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.common.data.User; public interface SimpleTbEntityService { - T save(T entity, SecurityUser user) throws ThingsboardException; + default T save(T entity) throws Exception { + return save(entity, null); + } - void delete (T entity, SecurityUser user) throws ThingsboardException; + T save(T entity, User user) throws Exception; + + void delete(T entity, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java index c1131761dc..7e929254a6 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.entitiy; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; @@ -31,75 +32,81 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; public interface TbNotificationEntityService { - void notifyEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, - ActionType actionType, SecurityUser user, Exception e, - Object... additionalInfo); + void logEntityAction(TenantId tenantId, I entityId, ActionType actionType, User user, + Exception e, Object... additionalInfo); + + void logEntityAction(TenantId tenantId, I entityId, E entity, ActionType actionType, + User user, Object... additionalInfo); + + void logEntityAction(TenantId tenantId, I entityId, E entity, ActionType actionType, + User user, Exception e, Object... additionalInfo); + + void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, User user, Object... additionalInfo); + + void logEntityAction(TenantId tenantId, I entityId, E entity, CustomerId customerId, + ActionType actionType, User user, Exception e, + Object... additionalInfo); void notifyCreateOrUpdateEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, ActionType actionType, - SecurityUser user, Object... additionalInfo); + User user, Object... additionalInfo); void notifyDeleteEntity(TenantId tenantId, I entityId, E entity, CustomerId customerId, ActionType actionType, List relatedEdgeIds, - SecurityUser user, Object... additionalInfo); + User user, Object... additionalInfo); - void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId, - CustomerId customerId, List relatedEdgeIds, - SecurityUser user, String body, Object... additionalInfo); + void notifyDeleteAlarm(TenantId tenantId, Alarm alarm, EntityId originatorId, CustomerId customerId, + List relatedEdgeIds, User user, String body, Object... additionalInfo); void notifyDeleteRuleChain(TenantId tenantId, RuleChain ruleChain, - List relatedEdgeIds, SecurityUser user); + List relatedEdgeIds, User user); void notifySendMsgToEdgeService(TenantId tenantId, I entityId, EdgeEventActionType edgeEventActionType); void notifyAssignOrUnassignEntityToCustomer(TenantId tenantId, I entityId, CustomerId customerId, E entity, ActionType actionType, - EdgeEventActionType edgeActionType, - SecurityUser user, boolean sendToEdge, + User user, boolean sendToEdge, Object... additionalInfo); void notifyAssignOrUnassignEntityToEdge(TenantId tenantId, I entityId, CustomerId customerId, EdgeId edgeId, E entity, ActionType actionType, - SecurityUser user, Object... additionalInfo); - - void notifyCreateOruUpdateTenant(Tenant tenant, ComponentLifecycleEvent event); + User user, Object... additionalInfo); + void notifyCreateOrUpdateTenant(Tenant tenant, ComponentLifecycleEvent event); void notifyDeleteTenant(Tenant tenant); void notifyCreateOrUpdateDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - Device oldDevice, ActionType actionType, SecurityUser user, Object... additionalInfo); + Device oldDevice, ActionType actionType, User user, Object... additionalInfo); void notifyDeleteDevice(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - List relatedEdgeIds, SecurityUser user, Object... additionalInfo); + List relatedEdgeIds, User user, Object... additionalInfo); void notifyUpdateDeviceCredentials(TenantId tenantId, DeviceId deviceId, CustomerId customerId, Device device, - DeviceCredentials deviceCredentials, SecurityUser user); + DeviceCredentials deviceCredentials, User user); void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId, - Device device, Tenant tenant, SecurityUser user, Object... additionalInfo); + Device device, Tenant tenant, User user, Object... additionalInfo); void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, - SecurityUser user, Object... additionalInfo); + User user, Object... additionalInfo); - void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, SecurityUser user, Object... additionalInfo); + void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo); void notifyCreateOrUpdateOrDelete(TenantId tenantId, CustomerId customerId, - I entityId, E entity, SecurityUser user, - ActionType actionType, boolean sendNotifyMsgToEdge, Exception e, - Object... additionalInfo); - - void notifyCreateOrUpdateOrDeleteRelation(TenantId tenantId, CustomerId customerId, - EntityRelation relation, SecurityUser user, - ActionType actionType, Exception e, - Object... additionalInfo); + I entityId, E entity, User user, + ActionType actionType, boolean sendNotifyMsgToEdge, + Exception e, Object... additionalInfo); + + void notifyRelation(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user, + ActionType actionType, Object... additionalInfo); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 4930f54a5d..4f2b7fd5bc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -15,29 +15,31 @@ */ package org.thingsboard.server.service.entitiy.alarm; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; @Service -@TbCoreComponent @AllArgsConstructor public class DefaultTbAlarmService extends AbstractTbEntityService implements TbAlarmService { @Override - public Alarm save(Alarm alarm, SecurityUser user) throws ThingsboardException { + public Alarm save(Alarm alarm, User user) throws ThingsboardException { ActionType actionType = alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = alarm.getTenantId(); try { @@ -45,46 +47,41 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb notificationEntityService.notifyCreateOrUpdateAlarm(savedAlarm, actionType, user); return savedAlarm; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ALARM), alarm, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ALARM), alarm, actionType, user, e); + throw e; } } @Override - public void ack(Alarm alarm, SecurityUser user) throws ThingsboardException { - try { - long ackTs = System.currentTimeMillis(); - alarmService.ackAlarm(user.getTenantId(), alarm.getId(), ackTs).get(); + public ListenableFuture ack(Alarm alarm, User user) { + long ackTs = System.currentTimeMillis(); + ListenableFuture future = alarmService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs); + return Futures.transform(future, result -> { alarm.setAckTs(ackTs); alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_ACK, user); - } catch (Exception e) { - throw handleException(e); - } + return null; + }, MoreExecutors.directExecutor()); } @Override - public void clear(Alarm alarm, SecurityUser user) throws ThingsboardException { - try { - long clearTs = System.currentTimeMillis(); - alarmService.clearAlarm(user.getTenantId(), alarm.getId(), null, clearTs).get(); + public ListenableFuture clear(Alarm alarm, User user) { + long clearTs = System.currentTimeMillis(); + ListenableFuture future = alarmService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs); + return Futures.transform(future, result -> { alarm.setClearTs(clearTs); alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); notificationEntityService.notifyCreateOrUpdateAlarm(alarm, ActionType.ALARM_CLEAR, user); - } catch (Exception e) { - throw handleException(e); - } + return null; + }, MoreExecutors.directExecutor()); } @Override - public Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException { - try { - List relatedEdgeIds = findRelatedEdgeIds(user.getTenantId(), alarm.getOriginator()); - notificationEntityService.notifyDeleteAlarm(user.getTenantId(), alarm, alarm.getOriginator(), user.getCustomerId(), - relatedEdgeIds, user, JacksonUtil.OBJECT_MAPPER.writeValueAsString(alarm)); - return alarmService.deleteAlarm(user.getTenantId(), alarm.getId()).isSuccessful(); - } catch (Exception e) { - throw handleException(e); - } + public Boolean delete(Alarm alarm, User user) { + TenantId tenantId = alarm.getTenantId(); + List relatedEdgeIds = findRelatedEdgeIds(tenantId, alarm.getOriginator()); + notificationEntityService.notifyDeleteAlarm(tenantId, alarm, alarm.getOriginator(), alarm.getCustomerId(), + relatedEdgeIds, user, JacksonUtil.toString(alarm)); + return alarmService.deleteAlarm(tenantId, alarm.getId()).isSuccessful(); } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index 7a6d7f6a81..8cd8d0b49d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -15,17 +15,18 @@ */ package org.thingsboard.server.service.entitiy.alarm; +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.service.security.model.SecurityUser; public interface TbAlarmService { - Alarm save(Alarm entity, SecurityUser user) throws ThingsboardException; + Alarm save(Alarm entity, User user) throws ThingsboardException; - void ack(Alarm alarm, SecurityUser user) throws ThingsboardException; + ListenableFuture ack(Alarm alarm, User user); - void clear(Alarm alarm, SecurityUser user) throws ThingsboardException; + ListenableFuture clear(Alarm alarm, User user); - Boolean delete(Alarm alarm, SecurityUser user) throws ThingsboardException; + Boolean delete(Alarm alarm, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java index 492464f147..724c3bb5c1 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -20,116 +20,111 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; @Service -@TbCoreComponent @AllArgsConstructor public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService { + private final AssetService assetService; + @Override - public Asset save(Asset asset, SecurityUser user) throws ThingsboardException { + public Asset save(Asset asset, User user) throws Exception { ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = asset.getTenantId(); try { Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); - notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), asset, savedAsset.getCustomerId(), actionType, user); + autoCommit(user, savedAsset.getId()); + notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), savedAsset, + asset.getCustomerId(), actionType, user); return savedAsset; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), asset, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), asset, actionType, user, e); + throw e; } } @Override - public ListenableFuture delete(Asset asset, SecurityUser user) throws ThingsboardException { + public ListenableFuture delete(Asset asset, User user) { TenantId tenantId = asset.getTenantId(); AssetId assetId = asset.getId(); try { List relatedEdgeIds = findRelatedEdgeIds(tenantId, assetId); assetService.deleteAsset(tenantId, assetId); - notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), ActionType.DELETED, - relatedEdgeIds, user, assetId.toString()); + notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), + ActionType.DELETED, relatedEdgeIds, user, assetId.toString()); return removeAlarmsByEntityId(tenantId, assetId); } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, - ActionType.DELETED, user, e, assetId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), ActionType.DELETED, user, e, + assetId.toString()); + throw e; } } @Override - public Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException { + public Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; CustomerId customerId = customer.getId(); try { Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName()); + actionType, user, true, assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, - actionType, user, e, assetId.toString(), customerId.toString()); - - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, + assetId.toString(), customerId.toString()); + throw e; } } @Override - public Asset unassignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException { + public Asset unassignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, User user) throws ThingsboardException { ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; try { Asset savedAsset = checkNotNull(assetService.unassignAssetFromCustomer(tenantId, assetId)); CustomerId customerId = customer.getId(); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, customerId, savedAsset, - actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, - true, customerId.toString(), customer.getName()); + actionType, user, true, assetId.toString(), customerId.toString(), customer.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, - actionType, user, e, assetId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); + throw e; } } @Override - public Asset assignAssetToPublicCustomer(TenantId tenantId, AssetId assetId, SecurityUser user) throws ThingsboardException { + public Asset assignAssetToPublicCustomer(TenantId tenantId, AssetId assetId, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; try { Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Asset savedAsset = checkNotNull(assetService.assignAssetToCustomer(tenantId, assetId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, assetId, savedAsset.getCustomerId(), savedAsset, - actionType, null, user, false, actionType.toString(), - publicCustomer.getId().toString(), publicCustomer.getName()); + actionType, user, false, actionType.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedAsset; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, - actionType, user, e, assetId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, user, e, assetId.toString()); + throw e; } } @Override - public Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, Edge edge, SecurityUser user) throws ThingsboardException { + public Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, Edge edge, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_EDGE; EdgeId edgeId = edge.getId(); try { @@ -139,14 +134,14 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb return savedAsset; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, - actionType, user, e, assetId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, + user, e, assetId.toString(), edgeId.toString()); + throw e; } } @Override - public Asset unassignAssetFromEdge(TenantId tenantId, Asset asset, Edge edge, SecurityUser user) throws ThingsboardException { + public Asset unassignAssetFromEdge(TenantId tenantId, Asset asset, Edge edge, User user) throws ThingsboardException { ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; AssetId assetId = asset.getId(); EdgeId edgeId = edge.getId(); @@ -155,11 +150,12 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, assetId, asset.getCustomerId(), edgeId, asset, actionType, user, assetId.toString(), edgeId.toString(), edge.getName()); + return savedAsset; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ASSET), null, null, - actionType, user, e, assetId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), actionType, + user, e, assetId.toString(), edgeId.toString()); + throw e; } } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java index 3ffb03cb7b..303aabc35b 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/TbAssetService.java @@ -17,27 +17,27 @@ package org.thingsboard.server.service.entitiy.asset; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.security.model.SecurityUser; public interface TbAssetService { - Asset save(Asset asset, SecurityUser user) throws ThingsboardException; + Asset save(Asset asset, User user) throws Exception; - ListenableFuture delete(Asset asset, SecurityUser user) throws ThingsboardException; + ListenableFuture delete(Asset asset, User user); - Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException; + Asset assignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, User user) throws ThingsboardException; - Asset unassignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, SecurityUser user) throws ThingsboardException; + Asset unassignAssetToCustomer(TenantId tenantId, AssetId assetId, Customer customer, User user) throws ThingsboardException; - Asset assignAssetToPublicCustomer(TenantId tenantId, AssetId assetId, SecurityUser user) throws ThingsboardException; + Asset assignAssetToPublicCustomer(TenantId tenantId, AssetId assetId, User user) throws ThingsboardException; - Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, Edge edge, SecurityUser user) throws ThingsboardException; + Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, Edge edge, User user) throws ThingsboardException; - Asset unassignAssetFromEdge(TenantId tenantId, Asset asset, Edge edge, SecurityUser user) throws ThingsboardException; + Asset unassignAssetFromEdge(TenantId tenantId, Asset asset, Edge edge, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java index d35cb06a85..07ef6a2eef 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java @@ -19,41 +19,37 @@ import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; @Service -@TbCoreComponent @AllArgsConstructor public class DefaultTbCustomerService extends AbstractTbEntityService implements TbCustomerService { @Override - public Customer save(Customer customer, SecurityUser user) throws ThingsboardException { + public Customer save(Customer customer, User user) throws Exception { ActionType actionType = customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = customer.getTenantId(); - CustomerId customerId = customer.getId(); try { Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); - notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedCustomer.getId(), savedCustomer, customerId, actionType, user); + autoCommit(user, savedCustomer.getId()); + notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedCustomer.getId(), savedCustomer, null, actionType, user); return savedCustomer; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.CUSTOMER), customer, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), customer, actionType, user, e); + throw e; } } - @Override - public void delete(Customer customer, SecurityUser user) throws ThingsboardException { + public void delete(Customer customer, User user) { TenantId tenantId = customer.getTenantId(); CustomerId customerId = customer.getId(); try { @@ -63,9 +59,9 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements ActionType.DELETED, relatedEdgeIds, user, customerId.toString()); tbClusterService.broadcastEntityStateChangeEvent(tenantId, customer.getId(), ComponentLifecycleEvent.DELETED); } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.CUSTOMER), null, null, - ActionType.DELETED, user, e, customer.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), ActionType.DELETED, + user, e, customerId.toString()); + throw e; } } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java index f7110c4da7..5d9b4290f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java @@ -21,17 +21,17 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.HashSet; import java.util.List; @@ -42,23 +42,26 @@ import java.util.Set; @AllArgsConstructor public class DefaultTbDashboardService extends AbstractTbEntityService implements TbDashboardService { + private final DashboardService dashboardService; + @Override - public Dashboard save(Dashboard dashboard, SecurityUser user) throws ThingsboardException { + public Dashboard save(Dashboard dashboard, User user) throws Exception { ActionType actionType = dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = dashboard.getTenantId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); + autoCommit(user, savedDashboard.getId()); notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedDashboard.getId(), savedDashboard, null, actionType, user); return savedDashboard; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), dashboard, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), dashboard, actionType, user, e); + throw e; } } @Override - public void delete(Dashboard dashboard, SecurityUser user) throws ThingsboardException { + public void delete(Dashboard dashboard, User user) { DashboardId dashboardId = dashboard.getId(); TenantId tenantId = dashboard.getTenantId(); try { @@ -67,66 +70,70 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement notificationEntityService.notifyDeleteEntity(tenantId, dashboardId, dashboard, null, ActionType.DELETED, relatedEdgeIds, user, dashboardId.toString()); } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null, - ActionType.DELETED, user, e, dashboardId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), ActionType.DELETED, user, e, dashboardId.toString()); + throw e; } } @Override - public Dashboard assignDashboardToCustomer(DashboardId dashboardId, Customer customer, SecurityUser user) throws ThingsboardException { + public Dashboard assignDashboardToCustomer(Dashboard dashboard, Customer customer, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + TenantId tenantId = dashboard.getTenantId(); CustomerId customerId = customer.getId(); + DashboardId dashboardId = dashboard.getId(); try { - Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(user.getTenantId(), dashboardId, customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(user.getTenantId(), dashboardId, customerId, savedDashboard, - actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName()); + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, + actionType, user, true, dashboardId.toString(), customerId.toString(), customer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.notifyEntity(user.getTenantId(), emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboardId.toString(), customerId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, + user, e, dashboardId.toString(), customerId.toString()); + throw e; } } @Override - public Dashboard assignDashboardToPublicCustomer(DashboardId dashboardId, SecurityUser user) throws ThingsboardException { + public Dashboard assignDashboardToPublicCustomer(Dashboard dashboard, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + TenantId tenantId = dashboard.getTenantId(); + DashboardId dashboardId = dashboard.getId(); try { - Customer publicCustomer = customerService.findOrCreatePublicCustomer(user.getTenantId()); - Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(user.getTenantId(), dashboardId, publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(user.getTenantId(), dashboardId, user.getCustomerId(), savedDashboard, - actionType, null, user, false, dashboardId.toString(), + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, publicCustomer.getId())); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, publicCustomer.getId(), savedDashboard, + actionType, user, false, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.notifyEntity(user.getTenantId(), emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboardId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + throw e; } } @Override - public Dashboard unassignDashboardFromPublicCustomer(Dashboard dashboard, SecurityUser user) throws ThingsboardException { + public Dashboard unassignDashboardFromPublicCustomer(Dashboard dashboard, User user) throws ThingsboardException { ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; + TenantId tenantId = dashboard.getTenantId(); + DashboardId dashboardId = dashboard.getId(); try { - Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId()); - Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(user.getTenantId(), dashboard.getId(), publicCustomer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(user.getTenantId(), dashboard.getId(), user.getCustomerId(), dashboard, - actionType, null, user, false, dashboard.getId().toString(), + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, publicCustomer.getId())); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, publicCustomer.getId(), dashboard, + actionType, user, false, dashboardId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.notifyEntity(user.getTenantId(), emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboard.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + throw e; } } @Override - public Dashboard updateDashboardCustomers(Dashboard dashboard, Set customerIds, SecurityUser user) throws ThingsboardException { + public Dashboard updateDashboardCustomers(Dashboard dashboard, Set customerIds, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; - TenantId tenantId = user.getTenantId(); + TenantId tenantId = dashboard.getTenantId(); + DashboardId dashboardId = dashboard.getId(); try { Set addedCustomerIds = new HashSet<>(); Set removedCustomerIds = new HashSet<>(); @@ -150,94 +157,105 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement } else { Dashboard savedDashboard = null; for (CustomerId customerId : addedCustomerIds) { - savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId)); + savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, - actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerInfo.getTitle()); + actionType, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } + actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; for (CustomerId customerId : removedCustomerIds) { ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); - savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customerId)); + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, - ActionType.UNASSIGNED_FROM_CUSTOMER, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customerInfo.getTitle()); + ActionType.UNASSIGNED_FROM_CUSTOMER, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboard.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + throw e; } } @Override - public Dashboard addDashboardCustomers(Dashboard dashboard, Set customerIds, SecurityUser user) throws ThingsboardException { + public Dashboard addDashboardCustomers(Dashboard dashboard, Set customerIds, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; - TenantId tenantId = user.getTenantId(); + TenantId tenantId = dashboard.getTenantId(); + DashboardId dashboardId = dashboard.getId(); try { - if (customerIds.isEmpty()) { + Set addedCustomerIds = new HashSet<>(); + for (CustomerId customerId : customerIds) { + if (!dashboard.isAssignedToCustomer(customerId)) { + addedCustomerIds.add(customerId); + } + } + if (addedCustomerIds.isEmpty()) { return dashboard; } else { Dashboard savedDashboard = null; - for (CustomerId customerId : customerIds) { - savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId)); + for (CustomerId customerId : addedCustomerIds) { + savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(tenantId, dashboardId, customerId)); ShortCustomerInfo customerInfo = savedDashboard.getAssignedCustomerInfo(customerId); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, - actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerInfo.getTitle()); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, + actionType, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboard.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + throw e; } } @Override - public Dashboard removeDashboardCustomers(Dashboard dashboard, Set customerIds, SecurityUser user) throws ThingsboardException { + public Dashboard removeDashboardCustomers(Dashboard dashboard, Set customerIds, User user) throws ThingsboardException { ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; - TenantId tenantId = user.getTenantId(); + TenantId tenantId = dashboard.getTenantId(); + DashboardId dashboardId = dashboard.getId(); try { - if (customerIds.isEmpty()) { + Set removedCustomerIds = new HashSet<>(); + for (CustomerId customerId : customerIds) { + if (dashboard.isAssignedToCustomer(customerId)) { + removedCustomerIds.add(customerId); + } + } + if (removedCustomerIds.isEmpty()) { return dashboard; } else { Dashboard savedDashboard = null; - for (CustomerId customerId : customerIds) { + for (CustomerId customerId : removedCustomerIds) { ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId); - savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customerId)); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, savedDashboard.getId(), customerId, savedDashboard, - actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customerInfo.getTitle()); + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customerId)); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customerId, savedDashboard, + actionType, user, true, dashboardId.toString(), customerId.toString(), customerInfo.getTitle()); } return savedDashboard; } } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboard.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + throw e; } } @Override - public Dashboard asignDashboardToEdge(DashboardId dashboardId, Edge edge, SecurityUser user) throws ThingsboardException { + public Dashboard asignDashboardToEdge(TenantId tenantId, DashboardId dashboardId, Edge edge, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_EDGE; - TenantId tenantId = user.getTenantId(); EdgeId edgeId = edge.getId(); try { Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(tenantId, dashboardId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, user.getCustomerId(), + notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, null, edgeId, savedDashboard, actionType, user, dashboardId.toString(), edgeId.toString(), edge.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, dashboardId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + actionType, user, e, dashboardId.toString(), edgeId); + throw e; } } @Override - public Dashboard unassignDashboardFromEdge(Dashboard dashboard, Edge edge, SecurityUser user) throws ThingsboardException { + public Dashboard unassignDashboardFromEdge(Dashboard dashboard, Edge edge, User user) throws ThingsboardException { ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; TenantId tenantId = dashboard.getTenantId(); DashboardId dashboardId = dashboard.getId(); @@ -245,30 +263,30 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement try { Dashboard savedDevice = checkNotNull(dashboardService.unassignDashboardFromEdge(tenantId, dashboardId, edgeId)); - notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, user.getCustomerId(), + notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, dashboardId, null, edgeId, dashboard, actionType, user, dashboardId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboardId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, + dashboardId.toString(), edgeId.toString()); + throw e; } } @Override - public Dashboard unassignDashboardFromCustomer(Dashboard dashboard, Customer customer, SecurityUser user) throws ThingsboardException { + public Dashboard unassignDashboardFromCustomer(Dashboard dashboard, Customer customer, User user) throws ThingsboardException { ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; TenantId tenantId = dashboard.getTenantId(); + DashboardId dashboardId = dashboard.getId(); try { - Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customer.getId())); - notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboard.getId(), customer.getId(), savedDashboard, - actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customer.getId().toString(), customer.getName()); + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromCustomer(tenantId, dashboardId, customer.getId())); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, dashboardId, customer.getId(), savedDashboard, + actionType, user, true, dashboardId.toString(), customer.getId().toString(), customer.getName()); return savedDashboard; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DASHBOARD), null, null, - actionType, user, e, dashboard.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DASHBOARD), actionType, user, e, dashboardId.toString()); + throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java index 84dba247ff..73d7ea5d44 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/TbDashboardService.java @@ -17,33 +17,34 @@ package org.thingsboard.server.service.entitiy.dashboard; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.service.entitiy.SimpleTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.Set; -public interface TbDashboardService extends SimpleTbEntityService { +public interface TbDashboardService extends SimpleTbEntityService { - Dashboard assignDashboardToCustomer(DashboardId dashboardId, Customer customer, SecurityUser user) throws ThingsboardException; + Dashboard assignDashboardToCustomer(Dashboard dashboard, Customer customer, User user) throws ThingsboardException; - Dashboard assignDashboardToPublicCustomer(DashboardId dashboardId, SecurityUser user) throws ThingsboardException; + Dashboard assignDashboardToPublicCustomer(Dashboard dashboard, User user) throws ThingsboardException; - Dashboard unassignDashboardFromPublicCustomer(Dashboard dashboard, SecurityUser user) throws ThingsboardException; + Dashboard unassignDashboardFromPublicCustomer(Dashboard dashboard, User user) throws ThingsboardException; - Dashboard updateDashboardCustomers(Dashboard dashboard, Set customerIds, SecurityUser user) throws ThingsboardException; + Dashboard updateDashboardCustomers(Dashboard dashboard, Set customerIds, User user) throws ThingsboardException; - Dashboard addDashboardCustomers(Dashboard dashboard, Set customerIds, SecurityUser user) throws ThingsboardException; + Dashboard addDashboardCustomers(Dashboard dashboard, Set customerIds, User user) throws ThingsboardException; - Dashboard removeDashboardCustomers(Dashboard dashboard, Set customerIds, SecurityUser user) throws ThingsboardException; + Dashboard removeDashboardCustomers(Dashboard dashboard, Set customerIds, User user) throws ThingsboardException; - Dashboard asignDashboardToEdge(DashboardId dashboardId, Edge edge, SecurityUser user) throws ThingsboardException; + Dashboard asignDashboardToEdge(TenantId tenantId, DashboardId dashboardId, Edge edge, User user) throws ThingsboardException; - Dashboard unassignDashboardFromEdge(Dashboard dashboard, Edge edge, SecurityUser user) throws ThingsboardException; + Dashboard unassignDashboardFromEdge(Dashboard dashboard, Edge edge, User user) throws ThingsboardException; - Dashboard unassignDashboardFromCustomer(Dashboard dashboard, Customer customer, SecurityUser user) throws ThingsboardException; + Dashboard unassignDashboardFromCustomer(Dashboard dashboard, Customer customer, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index d74610a462..959e6520b1 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -25,21 +25,24 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.device.ClaimDevicesService; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; @@ -49,24 +52,32 @@ import java.util.List; @Slf4j public class DefaultTbDeviceService extends AbstractTbEntityService implements TbDeviceService { + private final DeviceService deviceService; + private final DeviceCredentialsService deviceCredentialsService; + private final ClaimDevicesService claimDevicesService; + private final TenantService tenantService; + @Override - public Device save(TenantId tenantId, Device device, Device oldDevice, String accessToken, SecurityUser user) throws ThingsboardException { + public Device save(Device device, Device oldDevice, String accessToken, User user) throws Exception { ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = device.getTenantId(); try { Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); + autoCommit(user, savedDevice.getId()); notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(), savedDevice, oldDevice, actionType, user); return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), device, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, actionType, user, e); + throw e; } } @Override - public Device saveDeviceWithCredentials(TenantId tenantId, Device device, DeviceCredentials credentials, SecurityUser user) throws ThingsboardException { + public Device saveDeviceWithCredentials(Device device, DeviceCredentials credentials, User user) throws ThingsboardException { ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = device.getTenantId(); try { Device savedDevice = checkNotNull(deviceService.saveDeviceWithCredentials(device, credentials)); notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(), @@ -74,13 +85,14 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), device, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, + actionType, user, e); + throw e; } } @Override - public ListenableFuture delete(Device device, SecurityUser user) throws ThingsboardException { + public ListenableFuture delete(Device device, User user) { TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { @@ -91,31 +103,31 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T return removeAlarmsByEntityId(tenantId, deviceId); } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - ActionType.DELETED, user, e, deviceId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), ActionType.DELETED, + user, e, deviceId.toString()); + throw e; } } @Override - public Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, SecurityUser user) throws ThingsboardException { + public Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; CustomerId customerId = customer.getId(); try { - Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(user.getTenantId(), deviceId, customerId)); + Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, - actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName()); + actionType, user, true, deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, deviceId.toString(), customerId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, user, + e, deviceId.toString(), customerId.toString()); + throw e; } } @Override - public Device unassignDeviceFromCustomer(Device device, Customer customer, SecurityUser user) throws ThingsboardException { + public Device unassignDeviceFromCustomer(Device device, Customer customer, User user) throws ThingsboardException { ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); @@ -124,55 +136,53 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T CustomerId customerId = customer.getId(); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, customerId, savedDevice, - actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, - true, customerId.toString(), customer.getName()); + actionType, user, true, deviceId.toString(), customerId.toString(), customer.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, deviceId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, + user, e, deviceId.toString()); + throw e; } } @Override - public Device assignDeviceToPublicCustomer(TenantId tenantId, DeviceId deviceId, SecurityUser user) throws ThingsboardException { + public Device assignDeviceToPublicCustomer(TenantId tenantId, DeviceId deviceId, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); try { - Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); Device savedDevice = checkNotNull(deviceService.assignDeviceToCustomer(tenantId, deviceId, publicCustomer.getId())); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, deviceId, savedDevice.getCustomerId(), savedDevice, - actionType, null, user, false, deviceId.toString(), + actionType, user, false, deviceId.toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, deviceId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), actionType, + user, e, deviceId.toString()); + throw e; } } @Override - public DeviceCredentials getDeviceCredentialsByDeviceId(Device device, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.CREDENTIALS_READ; + public DeviceCredentials getDeviceCredentialsByDeviceId(Device device, User user) throws ThingsboardException { TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { DeviceCredentials deviceCredentials = checkNotNull(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId)); - notificationEntityService.notifyEntity(tenantId, deviceId, device, device.getCustomerId(), - actionType, user, null, deviceId.toString()); + notificationEntityService.logEntityAction(tenantId, deviceId, device, device.getCustomerId(), + ActionType.CREDENTIALS_READ, user, deviceId.toString()); return deviceCredentials; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, deviceId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + ActionType.CREDENTIALS_READ, user, e, deviceId.toString()); + throw e; } } @Override - public DeviceCredentials updateDeviceCredentials(Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException { + public DeviceCredentials updateDeviceCredentials(Device device, DeviceCredentials deviceCredentials, User user) throws ThingsboardException { TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { @@ -180,69 +190,63 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T notificationEntityService.notifyUpdateDeviceCredentials(tenantId, deviceId, device.getCustomerId(), device, result, user); return result; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), ActionType.CREDENTIALS_UPDATED, user, e, deviceCredentials); - throw handleException(e); + throw e; } } @Override - public ListenableFuture claimDevice(TenantId tenantId, Device device, CustomerId customerId, String secretKey, SecurityUser user) throws ThingsboardException { - try { - ListenableFuture future = claimDevicesService.claimDevice(device, customerId, secretKey); + public ListenableFuture claimDevice(TenantId tenantId, Device device, CustomerId customerId, String secretKey, User user) { + ListenableFuture future = claimDevicesService.claimDevice(device, customerId, secretKey); - return Futures.transform(future, result -> { - if (result != null && result.getResponse().equals(ClaimResponse.SUCCESS)) { - notificationEntityService.notifyEntity(tenantId, device.getId(), result.getDevice(), customerId, - ActionType.ASSIGNED_TO_CUSTOMER, user, null, device.getId().toString(), customerId.toString(), - customerService.findCustomerById(tenantId, customerId).getName()); - } - return result; - }, MoreExecutors.directExecutor()); - } catch (Exception e) { - throw handleException(e); - } + return Futures.transform(future, result -> { + if (result != null && result.getResponse().equals(ClaimResponse.SUCCESS)) { + notificationEntityService.logEntityAction(tenantId, device.getId(), result.getDevice(), customerId, + ActionType.ASSIGNED_TO_CUSTOMER, user, device.getId().toString(), customerId.toString(), + customerService.findCustomerById(tenantId, customerId).getName()); + } + return result; + }, MoreExecutors.directExecutor()); } @Override - public ListenableFuture reclaimDevice(TenantId tenantId, Device device, SecurityUser user) throws ThingsboardException { - try { - ListenableFuture future = claimDevicesService.reClaimDevice(tenantId, device); + public ListenableFuture reclaimDevice(TenantId tenantId, Device device, User user) { + ListenableFuture future = claimDevicesService.reClaimDevice(tenantId, device); - return Futures.transform(future, result -> { - Customer unassignedCustomer = result.getUnassignedCustomer(); - if (unassignedCustomer != null) { - notificationEntityService.notifyEntity(tenantId, device.getId(), device, device.getCustomerId(), ActionType.UNASSIGNED_FROM_CUSTOMER, user, null, - device.getId().toString(), unassignedCustomer.getId().toString(), unassignedCustomer.getName()); - } - return result; - }, MoreExecutors.directExecutor()); - } catch (Exception e) { - throw handleException(e); - } + return Futures.transform(future, result -> { + Customer unassignedCustomer = result.getUnassignedCustomer(); + if (unassignedCustomer != null) { + notificationEntityService.logEntityAction(tenantId, device.getId(), device, device.getCustomerId(), + ActionType.UNASSIGNED_FROM_CUSTOMER, user, device.getId().toString(), + unassignedCustomer.getId().toString(), unassignedCustomer.getName()); + } + return result; + }, MoreExecutors.directExecutor()); } @Override - public Device assignDeviceToTenant(Device device, Tenant newTenant, SecurityUser user) throws ThingsboardException { + public Device assignDeviceToTenant(Device device, Tenant newTenant, User user) { TenantId tenantId = device.getTenantId(); TenantId newTenantId = newTenant.getId(); + DeviceId deviceId = device.getId(); try { Tenant tenant = tenantService.findTenantById(tenantId); Device assignedDevice = deviceService.assignDeviceToTenant(newTenantId, device); - notificationEntityService.notifyAssignDeviceToTenant(tenantId, newTenantId, device.getId(), + notificationEntityService.notifyAssignDeviceToTenant(tenantId, newTenantId, deviceId, assignedDevice.getCustomerId(), assignedDevice, tenant, user, newTenantId.toString(), newTenant.getName()); return assignedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - ActionType.ASSIGNED_TO_TENANT, user, e, newTenantId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + ActionType.ASSIGNED_TO_TENANT, user, e, deviceId.toString()); + throw e; } } @Override - public Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, Edge edge, SecurityUser user) throws ThingsboardException { + public Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, Edge edge, User user) throws ThingsboardException { ActionType actionType = ActionType.ASSIGNED_TO_EDGE; EdgeId edgeId = edge.getId(); try { @@ -251,15 +255,14 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T edgeId, savedDevice, actionType, user, deviceId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, deviceId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + ActionType.ASSIGNED_TO_EDGE, user, e, deviceId.toString(), edgeId.toString()); + throw e; } } @Override - public Device unassignDeviceFromEdge(Device device, Edge edge, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; + public Device unassignDeviceFromEdge(Device device, Edge edge, User user) throws ThingsboardException { TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); EdgeId edgeId = edge.getId(); @@ -267,12 +270,12 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(tenantId, deviceId, edgeId)); notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, deviceId, device.getCustomerId(), - edgeId, device, actionType, user, deviceId.toString(), edgeId.toString(), edge.getName()); + edgeId, device, ActionType.UNASSIGNED_FROM_EDGE, user, deviceId.toString(), edgeId.toString(), edge.getName()); return savedDevice; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, deviceId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), + ActionType.UNASSIGNED_FROM_EDGE, user, e, deviceId.toString(), edgeId.toString()); + throw e; } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java index 74e0bcfbe0..3ffc120067 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/TbDeviceService.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; @@ -27,33 +28,32 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult; -import org.thingsboard.server.service.security.model.SecurityUser; public interface TbDeviceService { - Device save(TenantId tenantId, Device device, Device oldDevice, String accessToken, SecurityUser user) throws ThingsboardException; + Device save(Device device, Device oldDevice, String accessToken, User user) throws Exception; - Device saveDeviceWithCredentials(TenantId tenantId, Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException; + Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials, User user) throws ThingsboardException; - ListenableFuture delete(Device device, SecurityUser user) throws ThingsboardException; + ListenableFuture delete(Device device, User user); - Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, SecurityUser user) throws ThingsboardException; + Device assignDeviceToCustomer(TenantId tenantId, DeviceId deviceId, Customer customer, User user) throws ThingsboardException; - Device unassignDeviceFromCustomer(Device device, Customer customer, SecurityUser user) throws ThingsboardException; + Device unassignDeviceFromCustomer(Device device, Customer customer, User user) throws ThingsboardException; - Device assignDeviceToPublicCustomer(TenantId tenantId, DeviceId deviceId, SecurityUser user) throws ThingsboardException; + Device assignDeviceToPublicCustomer(TenantId tenantId, DeviceId deviceId, User user) throws ThingsboardException; - DeviceCredentials getDeviceCredentialsByDeviceId(Device device, SecurityUser user) throws ThingsboardException; + DeviceCredentials getDeviceCredentialsByDeviceId(Device device, User user) throws ThingsboardException; - DeviceCredentials updateDeviceCredentials(Device device, DeviceCredentials deviceCredentials, SecurityUser user) throws ThingsboardException; + DeviceCredentials updateDeviceCredentials(Device device, DeviceCredentials deviceCredentials, User user) throws ThingsboardException; - ListenableFuture claimDevice(TenantId tenantId, Device device, CustomerId customerId, String secretKey, SecurityUser user) throws ThingsboardException; + ListenableFuture claimDevice(TenantId tenantId, Device device, CustomerId customerId, String secretKey, User user); - ListenableFuture reclaimDevice(TenantId tenantId, Device device, SecurityUser user) throws ThingsboardException; + ListenableFuture reclaimDevice(TenantId tenantId, Device device, User user); - Device assignDeviceToTenant(Device device, Tenant newTenant, SecurityUser user) throws ThingsboardException; + Device assignDeviceToTenant(Device device, Tenant newTenant, User user); - Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, Edge edge, SecurityUser user) throws ThingsboardException; + Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, Edge edge, User user) throws ThingsboardException; - Device unassignDeviceFromEdge(Device device, Edge edge, SecurityUser user) throws ThingsboardException; + Device unassignDeviceFromEdge(Device device, Edge edge, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/deviceProfile/DefaultTbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java similarity index 67% rename from application/src/main/java/org/thingsboard/server/service/entitiy/deviceProfile/DefaultTbDeviceProfileService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java index ca73b0cb83..650b35db0d 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/deviceProfile/DefaultTbDeviceProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java @@ -13,21 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.deviceProfile; +package org.thingsboard.server.service.entitiy.device.profile; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.ota.OtaPackageStateService; import java.util.Objects; @@ -36,8 +38,12 @@ import java.util.Objects; @AllArgsConstructor @Slf4j public class DefaultTbDeviceProfileService extends AbstractTbEntityService implements TbDeviceProfileService { + + private final DeviceProfileService deviceProfileService; + private final OtaPackageStateService otaPackageStateService; + @Override - public DeviceProfile save(DeviceProfile deviceProfile, SecurityUser user) throws ThingsboardException { + public DeviceProfile save(DeviceProfile deviceProfile, User user) throws Exception { ActionType actionType = deviceProfile.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = deviceProfile.getTenantId(); try { @@ -54,23 +60,24 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple } } DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); - + autoCommit(user, savedDeviceProfile.getId()); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedDeviceProfile.getId(), actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); otaPackageStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedDeviceProfile.getId(), savedDeviceProfile, user, actionType, true, null); + notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedDeviceProfile.getId(), + savedDeviceProfile, user, actionType, true, null); return savedDeviceProfile; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.DEVICE_PROFILE), deviceProfile, user, actionType, false, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), deviceProfile, actionType, user, e); + throw e; } } @Override - public void delete(DeviceProfile deviceProfile, SecurityUser user) throws ThingsboardException { + public void delete(DeviceProfile deviceProfile, User user) { DeviceProfileId deviceProfileId = deviceProfile.getId(); TenantId tenantId = deviceProfile.getTenantId(); try { @@ -78,34 +85,35 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple tbClusterService.onDeviceProfileDelete(deviceProfile, null); tbClusterService.broadcastEntityStateChangeEvent(tenantId, deviceProfileId, ComponentLifecycleEvent.DELETED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, deviceProfileId, deviceProfile, user, ActionType.DELETED, true, null, deviceProfileId.toString()); + notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, deviceProfileId, deviceProfile, + user, ActionType.DELETED, true, null, deviceProfileId.toString()); } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.DEVICE_PROFILE), null, user, ActionType.DELETED, false, e, deviceProfileId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), ActionType.DELETED, + user, e, deviceProfileId.toString()); + throw e; } } @Override - public DeviceProfile setDefaultDeviceProfile(DeviceProfile deviceProfile, DeviceProfile previousDefaultDeviceProfile, SecurityUser user) throws ThingsboardException { + public DeviceProfile setDefaultDeviceProfile(DeviceProfile deviceProfile, DeviceProfile previousDefaultDeviceProfile, User user) throws ThingsboardException { TenantId tenantId = deviceProfile.getTenantId(); + DeviceProfileId deviceProfileId = deviceProfile.getId(); try { - - if (deviceProfileService.setDefaultDeviceProfile(tenantId, deviceProfile.getId())) { + if (deviceProfileService.setDefaultDeviceProfile(tenantId, deviceProfileId)) { if (previousDefaultDeviceProfile != null) { previousDefaultDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, previousDefaultDeviceProfile.getId()); - notificationEntityService.notifyEntity(tenantId, previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile, null, - ActionType.UPDATED, user, null); + notificationEntityService.logEntityAction(tenantId, previousDefaultDeviceProfile.getId(), previousDefaultDeviceProfile, + ActionType.UPDATED, user); } - deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfile.getId()); + deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); - notificationEntityService.notifyEntity(tenantId, deviceProfile.getId(), deviceProfile, null, - ActionType.UPDATED, user, null); + notificationEntityService.logEntityAction(tenantId, deviceProfileId, deviceProfile, ActionType.UPDATED, user); } return deviceProfile; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE_PROFILE), null, null, - ActionType.UPDATED, user, e, deviceProfile.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE_PROFILE), ActionType.UPDATED, + user, e, deviceProfileId.toString()); + throw e; } } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/deviceProfile/TbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/TbDeviceProfileService.java similarity index 81% rename from application/src/main/java/org/thingsboard/server/service/entitiy/deviceProfile/TbDeviceProfileService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/TbDeviceProfileService.java index 28b07e7d13..03ad8f75cb 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/deviceProfile/TbDeviceProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/TbDeviceProfileService.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.deviceProfile; +package org.thingsboard.server.service.entitiy.device.profile; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.service.entitiy.SimpleTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; public interface TbDeviceProfileService extends SimpleTbEntityService { - DeviceProfile setDefaultDeviceProfile(DeviceProfile deviceProfile, DeviceProfile previousDefaultDeviceProfile, SecurityUser user) throws ThingsboardException; + DeviceProfile setDefaultDeviceProfile(DeviceProfile deviceProfile, DeviceProfile previousDefaultDeviceProfile, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java index dca6869cb9..b06466b01f 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -28,9 +29,10 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; @AllArgsConstructor @TbCoreComponent @@ -38,8 +40,11 @@ import org.thingsboard.server.service.security.model.SecurityUser; @Slf4j public class DefaultTbEdgeService extends AbstractTbEntityService implements TbEdgeService { + private final EdgeNotificationService edgeNotificationService; + private final RuleChainService ruleChainService; + @Override - public Edge save(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException { + public Edge save(Edge edge, RuleChain edgeTemplateRootRuleChain, User user) throws Exception { ActionType actionType = edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = edge.getTenantId(); try { @@ -56,95 +61,89 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE return savedEdge; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), edge, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), edge, actionType, user, e); + throw e; } } @Override - public void delete(Edge edge, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.DELETED; + public void delete(Edge edge, User user) { EdgeId edgeId = edge.getId(); TenantId tenantId = edge.getTenantId(); try { edgeService.deleteEdge(tenantId, edgeId); - notificationEntityService.notifyEdge(tenantId, edgeId, edge.getCustomerId(), edge, actionType, user, edgeId.toString()); + notificationEntityService.notifyEdge(tenantId, edgeId, edge.getCustomerId(), edge, ActionType.DELETED, user, edgeId.toString()); } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), edge, null, actionType, + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), ActionType.DELETED, user, e, edgeId.toString()); - throw handleException(e); + throw e; } } @Override - public Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + public Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, User user) throws ThingsboardException { CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); - - notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, actionType, user, + notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, ActionType.ASSIGNED_TO_CUSTOMER, user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, - actionType, user, e, edgeId.toString(), customerId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + ActionType.ASSIGNED_TO_CUSTOMER, user, e, edgeId.toString(), customerId.toString()); + throw e; } } @Override - public Edge unassignEdgeFromCustomer(Edge edge, Customer customer, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; + public Edge unassignEdgeFromCustomer(Edge edge, Customer customer, User user) throws ThingsboardException { TenantId tenantId = edge.getTenantId(); EdgeId edgeId = edge.getId(); CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(tenantId, edgeId)); - notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, actionType, user, + notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, ActionType.UNASSIGNED_FROM_CUSTOMER, user, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, - actionType, user, e, edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + ActionType.UNASSIGNED_FROM_CUSTOMER, user, e, edgeId.toString()); + throw e; } } @Override - public Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + public Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, User user) throws ThingsboardException { + Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); + CustomerId customerId = publicCustomer.getId(); try { - Customer publicCustomer = customerService.findOrCreatePublicCustomer(tenantId); - CustomerId customerId = publicCustomer.getId(); Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); - notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, actionType, user, + notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, ActionType.ASSIGNED_TO_CUSTOMER, user, edgeId.toString(), customerId.toString(), publicCustomer.getName()); return savedEdge; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, - actionType, user, e, edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + ActionType.ASSIGNED_TO_CUSTOMER, user, e, edgeId.toString()); + throw e; } } @Override - public Edge setEdgeRootRuleChain(Edge edge, RuleChainId ruleChainId, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.UPDATED; + public Edge setEdgeRootRuleChain(Edge edge, RuleChainId ruleChainId, User user) throws Exception { TenantId tenantId = edge.getTenantId(); EdgeId edgeId = edge.getId(); try { Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, ruleChainId); - notificationEntityService.notifyEdge(tenantId, edgeId, null, updatedEdge, actionType, user); + notificationEntityService.notifyEdge(tenantId, edgeId, null, updatedEdge, ActionType.UPDATED, user); return updatedEdge; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.EDGE), null, null, - actionType, user, e, edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), + ActionType.UPDATED, user, e, edgeId.toString()); + throw e; } } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java index 3b4fc28283..b7cc0a5072 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java @@ -16,24 +16,24 @@ package org.thingsboard.server.service.entitiy.edge; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.service.security.model.SecurityUser; public interface TbEdgeService { - Edge save(Edge edge, RuleChain edgeTemplateRootRuleChain, SecurityUser user) throws ThingsboardException; + Edge save(Edge edge, RuleChain edgeTemplateRootRuleChain, User user) throws Exception; - void delete(Edge edge, SecurityUser user) throws ThingsboardException; + void delete(Edge edge, User user); - Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, SecurityUser user) throws ThingsboardException; + Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, User user) throws ThingsboardException; - Edge unassignEdgeFromCustomer(Edge edge, Customer customer, SecurityUser user) throws ThingsboardException; + Edge unassignEdgeFromCustomer(Edge edge, Customer customer, User user) throws ThingsboardException; - Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, SecurityUser user) throws ThingsboardException; + Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, User user) throws ThingsboardException; - Edge setEdgeRootRuleChain(Edge edge, RuleChainId ruleChainId, SecurityUser user) throws ThingsboardException; + Edge setEdgeRootRuleChain(Edge edge, RuleChainId ruleChainId, User user) throws Exception; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityRelation/DefaultTbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/service/entitiy/entityRelation/DefaultTbEntityRelationService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java index ea895683a1..8a32392ebc 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityRelation/DefaultTbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/DefaultTbEntityRelationService.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.entityRelation; +package org.thingsboard.server.service.entitiy.entity.relation; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -25,52 +26,60 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; @Service @TbCoreComponent @AllArgsConstructor @Slf4j public class DefaultTbEntityRelationService extends AbstractTbEntityService implements TbEntityRelationService { + + private final RelationService relationService; + @Override - public void save(TenantId tenantId, CustomerId customerId, EntityRelation relation, SecurityUser user) throws ThingsboardException { + public void save(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user) throws ThingsboardException { try { relationService.saveRelation(tenantId, relation); - notificationEntityService.notifyCreateOrUpdateOrDeleteRelation (tenantId, customerId, - relation, user, ActionType.RELATION_ADD_OR_UPDATE, null, relation); + notificationEntityService.notifyRelation(tenantId, customerId, + relation, user, ActionType.RELATION_ADD_OR_UPDATE, relation); } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDeleteRelation (tenantId, customerId, - relation, user, ActionType.RELATION_ADD_OR_UPDATE, e, relation); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, relation.getFrom(), null, customerId, + ActionType.RELATION_ADD_OR_UPDATE, user, e, relation); + notificationEntityService.logEntityAction(tenantId, relation.getTo(), null, customerId, + ActionType.RELATION_ADD_OR_UPDATE, user, e, relation); + throw e; } } @Override - public void delete(TenantId tenantId, CustomerId customerId, EntityRelation relation, SecurityUser user) throws ThingsboardException { + public void delete(TenantId tenantId, CustomerId customerId, EntityRelation relation, User user) throws ThingsboardException { try { - Boolean found = relationService.deleteRelation(tenantId, relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup()); + boolean found = relationService.deleteRelation(tenantId, relation.getFrom(), relation.getTo(), relation.getType(), relation.getTypeGroup()); if (!found) { throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND); } - notificationEntityService.notifyCreateOrUpdateOrDeleteRelation (tenantId, customerId, - relation, user, ActionType.RELATION_DELETED, null, relation); + notificationEntityService.notifyRelation(tenantId, customerId, + relation, user, ActionType.RELATION_DELETED, relation); } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDeleteRelation (tenantId, customerId, - relation, user, ActionType.RELATION_DELETED, e, relation); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, relation.getFrom(), null, customerId, + ActionType.RELATION_DELETED, user, e, relation); + notificationEntityService.logEntityAction(tenantId, relation.getTo(), null, customerId, + ActionType.RELATION_DELETED, user, e, relation); + throw e; } } @Override - public void deleteRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, SecurityUser user) throws ThingsboardException { + public void deleteRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException { try { relationService.deleteEntityRelations(tenantId, entityId); - notificationEntityService.notifyEntity(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user, null); + notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user); } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, entityId, null, customerId, ActionType.RELATIONS_DELETED, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, entityId, null, customerId, + ActionType.RELATIONS_DELETED, user, e); + throw e; } - } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityRelation/TbEntityRelationService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java similarity index 69% rename from application/src/main/java/org/thingsboard/server/service/entitiy/entityRelation/TbEntityRelationService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java index 52ad888dc8..09dfd35fe4 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityRelation/TbEntityRelationService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entity/relation/TbEntityRelationService.java @@ -13,21 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.entityRelation; +package org.thingsboard.server.service.entitiy.entity.relation; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.service.security.model.SecurityUser; public interface TbEntityRelationService { - void save(TenantId tenantId, CustomerId customerId, EntityRelation entity, SecurityUser user) throws ThingsboardException; + void save(TenantId tenantId, CustomerId customerId, EntityRelation entity, User user) throws ThingsboardException; - void delete (TenantId tenantId, CustomerId customerId, EntityRelation entity, SecurityUser user) throws ThingsboardException; + void delete(TenantId tenantId, CustomerId customerId, EntityRelation entity, User user) throws ThingsboardException; - void deleteRelations (TenantId tenantId, CustomerId customerId, EntityId entityId, SecurityUser user) throws ThingsboardException; + void deleteRelations(TenantId tenantId, CustomerId customerId, EntityId entityId, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityView/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java similarity index 59% rename from application/src/main/java/org/thingsboard/server/service/entitiy/entityView/DefaultTbEntityViewService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index 878c6d32bb..e098d6b605 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityView/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.entityView; +package org.thingsboard.server.service.entitiy.entityview; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -23,13 +23,14 @@ import com.google.common.util.concurrent.SettableFuture; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.ConcurrentReferenceHashMap; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; @@ -40,73 +41,91 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; @Service -@TbCoreComponent @AllArgsConstructor @Slf4j public class DefaultTbEntityViewService extends AbstractTbEntityService implements TbEntityViewService { + private final EntityViewService entityViewService; + private final AttributesService attributesService; + private final TelemetrySubscriptionService tsSubService; private final TimeseriesService tsService; + final Map>> localCache = new ConcurrentHashMap<>(); + @Override - public EntityView save(EntityView entityView, EntityView existingEntityView, SecurityUser user) throws ThingsboardException { + public EntityView save(EntityView entityView, EntityView existingEntityView, User user) throws Exception { ActionType actionType = entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = entityView.getTenantId(); try { - List> futures = new ArrayList<>(); - if (existingEntityView != null) { - if (existingEntityView.getKeys() != null && existingEntityView.getKeys().getAttributes() != null) { - futures.add(deleteAttributesFromEntityView(existingEntityView, DataConstants.CLIENT_SCOPE, existingEntityView.getKeys().getAttributes().getCs(), user)); - futures.add(deleteAttributesFromEntityView(existingEntityView, DataConstants.SERVER_SCOPE, existingEntityView.getKeys().getAttributes().getCs(), user)); - futures.add(deleteAttributesFromEntityView(existingEntityView, DataConstants.SHARED_SCOPE, existingEntityView.getKeys().getAttributes().getCs(), user)); - } - List tsKeys = existingEntityView.getKeys() != null && existingEntityView.getKeys().getTimeseries() != null ? - existingEntityView.getKeys().getTimeseries() : Collections.emptyList(); - futures.add(deleteLatestFromEntityView(existingEntityView, tsKeys, user)); - } EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); - if (savedEntityView.getKeys() != null) { - if (savedEntityView.getKeys().getAttributes() != null) { - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), user)); - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), user)); - futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), user)); - } - futures.add(copyLatestFromEntityToEntityView(savedEntityView, user)); - } - for (ListenableFuture future : futures) { - try { - future.get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException("Failed to copy attributes to entity view", e); - } - } - + this.updateEntityViewAttributes(tenantId, savedEntityView, existingEntityView, user); + autoCommit(user, savedEntityView.getId()); notificationEntityService.notifyCreateOrUpdateEntity(savedEntityView.getTenantId(), savedEntityView.getId(), savedEntityView, null, actionType, user); - + localCache.computeIfAbsent(savedEntityView.getTenantId(), (k) -> new ConcurrentReferenceHashMap<>()).clear(); + tbClusterService.broadcastEntityStateChangeEvent(savedEntityView.getTenantId(), savedEntityView.getId(), + entityView.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return savedEntityView; } catch (Exception e) { - notificationEntityService.notifyEntity(user.getTenantId(), emptyId(EntityType.ENTITY_VIEW), entityView, null, actionType, user, e); - throw handleException(e); + notificationEntityService.logEntityAction(user.getTenantId(), emptyId(EntityType.ENTITY_VIEW), entityView, actionType, user, e); + throw e; + } + } + + @Override + public void updateEntityViewAttributes(TenantId tenantId, EntityView savedEntityView, EntityView oldEntityView, User user) throws ThingsboardException { + List> futures = new ArrayList<>(); + + if (oldEntityView != null) { + if (oldEntityView.getKeys() != null && oldEntityView.getKeys().getAttributes() != null) { + futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.CLIENT_SCOPE, oldEntityView.getKeys().getAttributes().getCs(), user)); + futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SERVER_SCOPE, oldEntityView.getKeys().getAttributes().getSs(), user)); + futures.add(deleteAttributesFromEntityView(oldEntityView, DataConstants.SHARED_SCOPE, oldEntityView.getKeys().getAttributes().getSh(), user)); + } + List tsKeys = oldEntityView.getKeys() != null && oldEntityView.getKeys().getTimeseries() != null ? + oldEntityView.getKeys().getTimeseries() : Collections.emptyList(); + futures.add(deleteLatestFromEntityView(oldEntityView, tsKeys, user)); + } + if (savedEntityView.getKeys() != null) { + if (savedEntityView.getKeys().getAttributes() != null) { + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.CLIENT_SCOPE, savedEntityView.getKeys().getAttributes().getCs(), user)); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SERVER_SCOPE, savedEntityView.getKeys().getAttributes().getSs(), user)); + futures.add(copyAttributesFromEntityToEntityView(savedEntityView, DataConstants.SHARED_SCOPE, savedEntityView.getKeys().getAttributes().getSh(), user)); + } + futures.add(copyLatestFromEntityToEntityView(tenantId, savedEntityView)); + } + for (ListenableFuture future : futures) { + try { + future.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException("Failed to copy attributes to entity view", e); + } } } @Override - public void delete(EntityView entityView, SecurityUser user) throws ThingsboardException { + public void delete(EntityView entityView, User user) throws ThingsboardException { TenantId tenantId = entityView.getTenantId(); EntityViewId entityViewId = entityView.getId(); try { @@ -114,99 +133,142 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen entityViewService.deleteEntityView(tenantId, entityViewId); notificationEntityService.notifyDeleteEntity(tenantId, entityViewId, entityView, entityView.getCustomerId(), ActionType.DELETED, relatedEdgeIds, user, entityViewId.toString()); + + localCache.computeIfAbsent(tenantId, (k) -> new ConcurrentReferenceHashMap<>()).clear(); + tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityViewId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ENTITY_VIEW), null, null, + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), ActionType.DELETED, user, e, entityViewId.toString()); - throw handleException(e); + throw e; } } @Override - public EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + public EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException { CustomerId customerId = customer.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, customerId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customerId, savedEntityView, - actionType, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, user, true, customerId.toString(), customer.getName()); + ActionType.ASSIGNED_TO_CUSTOMER, user, true, entityViewId.toString(), customerId.toString(), customer.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ENTITY_VIEW), null, null, - actionType, user, e, entityViewId.toString(), customerId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + ActionType.ASSIGNED_TO_CUSTOMER, user, e, entityViewId.toString(), customerId.toString()); + throw e; } } @Override public EntityView assignEntityViewToPublicCustomer(TenantId tenantId, CustomerId customerId, Customer publicCustomer, - EntityViewId entityViewId, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; + EntityViewId entityViewId, User user) throws ThingsboardException { try { EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToCustomer(tenantId, entityViewId, publicCustomer.getId())); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customerId, savedEntityView, - actionType, null, user, false, savedEntityView.getEntityId().toString(), + ActionType.ASSIGNED_TO_CUSTOMER, user, false, savedEntityView.getEntityId().toString(), publicCustomer.getId().toString(), publicCustomer.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ENTITY_VIEW), null, null, - actionType, user, e, entityViewId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + ActionType.ASSIGNED_TO_CUSTOMER, user, e, entityViewId.toString()); + throw e; } } @Override - public EntityView assignEntityViewToEdge(TenantId tenantId, CustomerId customerId, EntityViewId entityViewId, Edge edge, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.ASSIGNED_TO_EDGE; + public EntityView assignEntityViewToEdge(TenantId tenantId, CustomerId customerId, EntityViewId entityViewId, Edge edge, User user) throws ThingsboardException { EdgeId edgeId = edge.getId(); - EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToEdge(tenantId, entityViewId, edgeId)); try { + EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToEdge(tenantId, entityViewId, edgeId)); notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, entityViewId, customerId, - edgeId, savedEntityView, actionType, user, savedEntityView.getEntityId().toString(), + edgeId, savedEntityView, ActionType.ASSIGNED_TO_EDGE, user, savedEntityView.getEntityId().toString(), edgeId.toString(), edge.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.DEVICE), null, null, - actionType, user, e, entityViewId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + ActionType.ASSIGNED_TO_EDGE, user, e, entityViewId.toString(), edgeId.toString()); + throw e; } } @Override public EntityView unassignEntityViewFromEdge(TenantId tenantId, CustomerId customerId, EntityView entityView, - Edge edge, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.UNASSIGNED_FROM_EDGE; + Edge edge, User user) throws ThingsboardException { EntityViewId entityViewId = entityView.getId(); EdgeId edgeId = edge.getId(); try { EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromEdge(tenantId, entityViewId, edgeId)); notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, entityViewId, customerId, - edgeId, entityView, actionType, user, entityViewId.toString(), + edgeId, entityView, ActionType.UNASSIGNED_FROM_EDGE, user, entityViewId.toString(), edgeId.toString(), edge.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ENTITY_VIEW), null, null, - actionType, user, e, entityViewId.toString(), edgeId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + ActionType.UNASSIGNED_FROM_EDGE, user, e, entityViewId.toString(), edgeId.toString()); + throw e; } } @Override - public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, SecurityUser user) throws ThingsboardException { - ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; + public EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException { try { EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromCustomer(tenantId, entityViewId)); notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, entityViewId, customer.getId(), savedEntityView, - actionType, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customer.getId().toString(), customer.getName()); + ActionType.UNASSIGNED_FROM_CUSTOMER, user, true, customer.getId().toString(), customer.getName()); return savedEntityView; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.ENTITY_VIEW), null, null, - actionType, user, e, entityViewId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), + ActionType.UNASSIGNED_FROM_CUSTOMER, user, e, entityViewId.toString()); + throw e; + } + } + + @Override + public ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId) { + Map> localCacheByTenant = localCache.computeIfAbsent(tenantId, (k) -> new ConcurrentReferenceHashMap<>()); + List fromLocalCache = localCacheByTenant.get(entityId); + if (fromLocalCache != null) { + return Futures.immediateFuture(fromLocalCache); + } + + ListenableFuture> future = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId); + + return Futures.transform(future, (entityViewList) -> { + localCacheByTenant.put(entityId, entityViewList); + return entityViewList; + }, MoreExecutors.directExecutor()); + } + + @Override + public void onComponentLifecycleMsg(ComponentLifecycleMsg componentLifecycleMsg) { + Map> localCacheByTenant = localCache.computeIfAbsent(componentLifecycleMsg.getTenantId(), (k) -> new ConcurrentReferenceHashMap<>()); + EntityViewId entityViewId = new EntityViewId(componentLifecycleMsg.getEntityId().getId()); + deleteOldCacheValue(localCacheByTenant, entityViewId); + if (componentLifecycleMsg.getEvent() != ComponentLifecycleEvent.DELETED) { + EntityView entityView = entityViewService.findEntityViewById(componentLifecycleMsg.getTenantId(), entityViewId); + if (entityView != null) { + localCacheByTenant.remove(entityView.getEntityId()); + } + } + } + + private void deleteOldCacheValue(Map> localCacheByTenant, EntityViewId entityViewId) { + for (var entry : localCacheByTenant.entrySet()) { + EntityView toDelete = null; + for (EntityView view : entry.getValue()) { + if (entityViewId.equals(view.getId())) { + toDelete = view; + break; + } + } + if (toDelete != null) { + entry.getValue().remove(toDelete); + break; + } } } - private ListenableFuture> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection keys, SecurityUser user) throws ThingsboardException { + private ListenableFuture> copyAttributesFromEntityToEntityView(EntityView entityView, String scope, Collection keys, User user) throws ThingsboardException { EntityViewId entityId = entityView.getId(); if (keys != null && !keys.isEmpty()) { ListenableFuture> getAttrFuture = attributesService.find(entityView.getTenantId(), entityView.getEntityId(), scope, keys); @@ -221,8 +283,8 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen long lastUpdateTs = attributeKvEntry.getLastUpdateTs(); return startTime == 0 && endTime == 0 || (endTime == 0 && startTime < lastUpdateTs) || - (startTime == 0 && endTime > lastUpdateTs) - ? true : startTime < lastUpdateTs && endTime > lastUpdateTs; + (startTime == 0 && endTime > lastUpdateTs) || + (startTime < lastUpdateTs && endTime > lastUpdateTs); }).collect(Collectors.toList()); tsSubService.saveAndNotify(entityView.getTenantId(), entityId, scope, attributes, new FutureCallback() { @Override @@ -251,7 +313,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen } } - private ListenableFuture> copyLatestFromEntityToEntityView(EntityView entityView, SecurityUser user) { + private ListenableFuture> copyLatestFromEntityToEntityView(TenantId tenantId, EntityView entityView) { EntityViewId entityId = entityView.getId(); List keys = entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null ? entityView.getKeys().getTimeseries() : Collections.emptyList(); @@ -259,7 +321,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen long endTs = entityView.getEndTimeMs() == 0 ? Long.MAX_VALUE : entityView.getEndTimeMs(); ListenableFuture> keysFuture; if (keys.isEmpty()) { - keysFuture = Futures.transform(tsService.findAllLatest(user.getTenantId(), + keysFuture = Futures.transform(tsService.findAllLatest(tenantId, entityView.getEntityId()), latest -> latest.stream().map(TsKvEntry::getKey).collect(Collectors.toList()), MoreExecutors.directExecutor()); } else { keysFuture = Futures.immediateFuture(keys); @@ -267,7 +329,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen ListenableFuture> latestFuture = Futures.transformAsync(keysFuture, fetchKeys -> { List queries = fetchKeys.stream().filter(key -> !isBlank(key)).map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, "DESC")).collect(Collectors.toList()); if (!queries.isEmpty()) { - return tsService.findAll(user.getTenantId(), entityView.getEntityId(), queries); + return tsService.findAll(tenantId, entityView.getEntityId(), queries); } else { return Futures.immediateFuture(null); } @@ -288,7 +350,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen }, MoreExecutors.directExecutor()); } - private ListenableFuture deleteAttributesFromEntityView(EntityView entityView, String scope, List keys, SecurityUser user) { + private ListenableFuture deleteAttributesFromEntityView(EntityView entityView, String scope, List keys, User user) { EntityViewId entityId = entityView.getId(); SettableFuture resultFuture = SettableFuture.create(); if (keys != null && !keys.isEmpty()) { @@ -319,7 +381,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen return resultFuture; } - private ListenableFuture deleteLatestFromEntityView(EntityView entityView, List keys, SecurityUser user) { + private ListenableFuture deleteLatestFromEntityView(EntityView entityView, List keys, User user) { EntityViewId entityId = entityView.getId(); SettableFuture resultFuture = SettableFuture.create(); if (keys != null && !keys.isEmpty()) { @@ -337,7 +399,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen @Override public void onFailure(Throwable t) { try { - logTimeseriesDeleted(entityView.getTenantId(),user, entityId, keys, t); + logTimeseriesDeleted(entityView.getTenantId(), user, entityId, keys, t); } catch (ThingsboardException e) { log.error("Failed to log timeseries delete", e); } @@ -370,16 +432,16 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen return resultFuture; } - private void logAttributesUpdated(TenantId tenantId, SecurityUser user, EntityId entityId, String scope, List attributes, Throwable e) throws ThingsboardException { - notificationEntityService.notifyEntity(tenantId, entityId, null, null, ActionType.ATTRIBUTES_UPDATED, user, toException(e), scope, attributes); + private void logAttributesUpdated(TenantId tenantId, User user, EntityId entityId, String scope, List attributes, Throwable e) throws ThingsboardException { + notificationEntityService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_UPDATED, user, toException(e), scope, attributes); } - private void logAttributesDeleted(TenantId tenantId, SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) throws ThingsboardException { - notificationEntityService.notifyEntity(tenantId, entityId, null, null, ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); + private void logAttributesDeleted(TenantId tenantId, User user, EntityId entityId, String scope, List keys, Throwable e) throws ThingsboardException { + notificationEntityService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_DELETED, user, toException(e), scope, keys); } - private void logTimeseriesDeleted(TenantId tenantId, SecurityUser user, EntityId entityId, List keys, Throwable e) throws ThingsboardException { - notificationEntityService.notifyEntity(tenantId, entityId, null, null, ActionType.TIMESERIES_DELETED, user, toException(e), keys); + private void logTimeseriesDeleted(TenantId tenantId, User user, EntityId entityId, List keys, Throwable e) throws ThingsboardException { + notificationEntityService.logEntityAction(tenantId, entityId, ActionType.TIMESERIES_DELETED, user, toException(e), keys); } public static Exception toException(Throwable error) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityView/TbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/service/entitiy/entityView/TbEntityViewService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java index dd5db9391b..c4dbedd2eb 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityView/TbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/TbEntityViewService.java @@ -13,35 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.entityView; +package org.thingsboard.server.service.entitiy.entityview; +import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.common.msg.plugin.ComponentLifecycleListener; -public interface TbEntityViewService { +import java.util.List; - EntityView save(EntityView entityView, EntityView existingEntityView, SecurityUser user) throws ThingsboardException; +public interface TbEntityViewService extends ComponentLifecycleListener { - void delete (EntityView entity, SecurityUser user) throws ThingsboardException; + EntityView save(EntityView entityView, EntityView existingEntityView, User user) throws Exception; - EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, - SecurityUser user) throws ThingsboardException; + void updateEntityViewAttributes(TenantId tenantId, EntityView savedEntityView, EntityView oldEntityView, User user) throws ThingsboardException; + + void delete(EntityView entity, User user) throws ThingsboardException; + + EntityView assignEntityViewToCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException; EntityView assignEntityViewToPublicCustomer(TenantId tenantId, CustomerId customerId, Customer publicCustomer, - EntityViewId entityViewId, SecurityUser user) throws ThingsboardException; + EntityViewId entityViewId, User user) throws ThingsboardException; + + EntityView assignEntityViewToEdge(TenantId tenantId, CustomerId customerId, EntityViewId entityViewId, Edge edge, User user) throws ThingsboardException; - EntityView assignEntityViewToEdge(TenantId tenantId, CustomerId customerId, EntityViewId entityViewId, Edge edge, - SecurityUser user) throws ThingsboardException; + EntityView unassignEntityViewFromEdge(TenantId tenantId, CustomerId customerId, EntityView entityView, Edge edge, User user) throws ThingsboardException; - EntityView unassignEntityViewFromEdge(TenantId tenantId, CustomerId customerId, EntityView entityView, - Edge edge, SecurityUser user) throws ThingsboardException; + EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException; - EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, - SecurityUser user) throws ThingsboardException; + ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/otaPackageController/DefaultTbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java similarity index 71% rename from application/src/main/java/org/thingsboard/server/service/entitiy/otaPackageController/DefaultTbOtaPackageService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java index 555041ed8f..00cf70db0e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/otaPackageController/DefaultTbOtaPackageService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java @@ -13,24 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.otaPackageController; +package org.thingsboard.server.service.entitiy.ota; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.StringUtils; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.nio.ByteBuffer; @@ -39,40 +40,31 @@ import java.nio.ByteBuffer; @AllArgsConstructor @Slf4j public class DefaultTbOtaPackageService extends AbstractTbEntityService implements TbOtaPackageService { + + private final OtaPackageService otaPackageService; + @Override - public OtaPackageInfo save(SaveOtaPackageInfoRequest saveOtaPackageInfoRequest, SecurityUser user) throws ThingsboardException { - TenantId tenantId = saveOtaPackageInfoRequest.getTenantId(); + public OtaPackageInfo save(SaveOtaPackageInfoRequest saveOtaPackageInfoRequest, User user) throws ThingsboardException { ActionType actionType = saveOtaPackageInfoRequest.getId() == null ? ActionType.ADDED : ActionType.UPDATED; + TenantId tenantId = saveOtaPackageInfoRequest.getTenantId(); try { OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(saveOtaPackageInfoRequest), saveOtaPackageInfoRequest.isUsesUrl()); - notificationEntityService.notifyEntity(tenantId, savedOtaPackageInfo.getId(), savedOtaPackageInfo, null, - actionType, user, null); + + boolean sendToEdge = savedOtaPackageInfo.hasUrl() || savedOtaPackageInfo.isHasData(); + notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackageInfo.getId(), + savedOtaPackageInfo, user, actionType, sendToEdge, null); + return savedOtaPackageInfo; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.OTA_PACKAGE), saveOtaPackageInfoRequest, null, + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), saveOtaPackageInfoRequest, actionType, user, e); - throw handleException(e); - } - } - - @Override - public void delete(OtaPackageInfo otaPackageInfo, SecurityUser user) throws ThingsboardException { - TenantId tenantId = otaPackageInfo.getTenantId(); - OtaPackageId otaPackageId = otaPackageInfo.getId(); - try { - otaPackageService.deleteOtaPackage(tenantId, otaPackageId); - notificationEntityService.notifyEntity(tenantId, otaPackageId, otaPackageInfo, null, - ActionType.DELETED, user, null, otaPackageInfo.getId().toString()); - } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.OTA_PACKAGE), null, null, - ActionType.DELETED, user, e, otaPackageInfo.getId().toString()); - throw handleException(e); + throw e; } } @Override public OtaPackageInfo saveOtaPackageData(OtaPackageInfo otaPackageInfo, String checksum, ChecksumAlgorithm checksumAlgorithm, - byte[] data, String filename, String contentType, SecurityUser user) throws ThingsboardException { + byte[] data, String filename, String contentType, User user) throws ThingsboardException { TenantId tenantId = otaPackageInfo.getTenantId(); OtaPackageId otaPackageId = otaPackageInfo.getId(); try { @@ -95,13 +87,28 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen otaPackage.setData(ByteBuffer.wrap(data)); otaPackage.setDataSize((long) data.length); OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage); - notificationEntityService.notifyEntity(tenantId, savedOtaPackage.getId(), savedOtaPackage, null, - ActionType.UPDATED, user, null); + notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackage.getId(), + savedOtaPackage, user, ActionType.UPDATED, true, null); return savedOtaPackage; } catch (Exception e) { - notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.OTA_PACKAGE), null, null, - ActionType.UPDATED, user, e, otaPackageId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), ActionType.UPDATED, + user, e, otaPackageId.toString()); + throw e; + } + } + + @Override + public void delete(OtaPackageInfo otaPackageInfo, User user) throws ThingsboardException { + TenantId tenantId = otaPackageInfo.getTenantId(); + OtaPackageId otaPackageId = otaPackageInfo.getId(); + try { + otaPackageService.deleteOtaPackage(tenantId, otaPackageId); + notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, otaPackageId, otaPackageInfo, + user, ActionType.DELETED, true, null, otaPackageInfo.getId().toString()); + } catch (Exception e) { + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), + ActionType.DELETED, user, e, otaPackageId.toString()); + throw e; } } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/otaPackageController/TbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/TbOtaPackageService.java similarity index 73% rename from application/src/main/java/org/thingsboard/server/service/entitiy/otaPackageController/TbOtaPackageService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/ota/TbOtaPackageService.java index 7a7d0e79f7..aec2f20d0c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/otaPackageController/TbOtaPackageService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/TbOtaPackageService.java @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.otaPackageController; +package org.thingsboard.server.service.entitiy.ota; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; -import org.thingsboard.server.service.security.model.SecurityUser; -public interface TbOtaPackageService { +public interface TbOtaPackageService { - OtaPackageInfo save(SaveOtaPackageInfoRequest saveOtaPackageInfoRequest, SecurityUser user) throws ThingsboardException; - - void delete(OtaPackageInfo otaPackageInfo, SecurityUser user) throws ThingsboardException; + OtaPackageInfo save(SaveOtaPackageInfoRequest saveOtaPackageInfoRequest, User user) throws ThingsboardException; OtaPackageInfo saveOtaPackageData(OtaPackageInfo otaPackageInfo, String checksum, ChecksumAlgorithm checksumAlgorithm, - byte[] data, String filename, String contentType, SecurityUser securityUser) throws ThingsboardException; + byte[] data, String filename, String contentType, User user) throws ThingsboardException; + + void delete(OtaPackageInfo otaPackageInfo, User user) throws ThingsboardException; + } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java index 6a5778a474..4355d998bb 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/queue/DefaultTbQueueService.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageLink; @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -46,12 +48,11 @@ import java.util.stream.Collectors; @TbCoreComponent @AllArgsConstructor public class DefaultTbQueueService extends AbstractTbEntityService implements TbQueueService { - private static final String MAIN = "Main"; private static final long DELETE_DELAY = 30; + private final QueueService queueService; private final TbClusterService tbClusterService; private final TbQueueAdmin tbQueueAdmin; - private final DeviceProfileService deviceProfileService; private final SchedulerComponent scheduler; @Override @@ -74,6 +75,8 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb onQueueUpdated(savedQueue, oldQueue); } + notificationEntityService.notifySendMsgToEdgeService(queue.getTenantId(), savedQueue.getId(), create ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); + return savedQueue; } @@ -146,6 +149,8 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb } } }, DELETE_DELAY, TimeUnit.SECONDS); + + notificationEntityService.notifySendMsgToEdgeService(queue.getTenantId(), queue.getId(), EdgeEventActionType.DELETED); } @Override @@ -198,35 +203,7 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb } tenantIds.forEach(tenantId -> { - Map> deviceProfileQueues; - - if (oldTenantProfile != null && !newTenantProfile.getId().equals(oldTenantProfile.getId()) || !toRemove.isEmpty()) { - List deviceProfiles = deviceProfileService.findDeviceProfiles(tenantId, new PageLink(Integer.MAX_VALUE)).getData(); - deviceProfileQueues = deviceProfiles.stream() - .filter(dp -> dp.getDefaultQueueId() != null) - .collect(Collectors.groupingBy(DeviceProfile::getDefaultQueueId)); - } else { - deviceProfileQueues = Collections.emptyMap(); - } - - Map createdQueues = toCreate.stream() - .map(key -> saveQueue(new Queue(tenantId, newQueues.get(key)))) - .collect(Collectors.toMap(Queue::getName, Queue::getId)); - - // assigning created queues to device profiles instead of system queues - if (oldTenantProfile != null && !oldTenantProfile.isIsolatedTbRuleEngine()) { - deviceProfileQueues.forEach((queueId, list) -> { - Queue queue = queueService.findQueueById(TenantId.SYS_TENANT_ID, queueId); - QueueId queueIdToAssign = createdQueues.get(queue.getName()); - if (queueIdToAssign == null) { - queueIdToAssign = createdQueues.get(MAIN); - } - for (DeviceProfile deviceProfile : list) { - deviceProfile.setDefaultQueueId(queueIdToAssign); - saveDeviceProfile(deviceProfile); - } - }); - } + toCreate.forEach(key -> saveQueue(new Queue(tenantId, newQueues.get(key)))); toUpdate.forEach(key -> { Queue queueToUpdate = new Queue(tenantId, newQueues.get(key)); @@ -234,9 +211,7 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb queueToUpdate.setId(foundQueue.getId()); queueToUpdate.setCreatedTime(foundQueue.getCreatedTime()); - if (queueToUpdate.equals(foundQueue)) { - //Queue not changed - } else { + if (!queueToUpdate.equals(foundQueue)) { saveQueue(queueToUpdate); } }); @@ -244,26 +219,9 @@ public class DefaultTbQueueService extends AbstractTbEntityService implements Tb toRemove.forEach(q -> { Queue queue = queueService.findQueueByTenantIdAndNameInternal(tenantId, q); QueueId queueIdForRemove = queue.getId(); - if (deviceProfileQueues.containsKey(queueIdForRemove)) { - Queue foundQueue = queueService.findQueueByTenantIdAndName(tenantId, q); - if (foundQueue == null || queue.equals(foundQueue)) { - foundQueue = queueService.findQueueByTenantIdAndName(tenantId, MAIN); - } - QueueId newQueueId = foundQueue.getId(); - deviceProfileQueues.get(queueIdForRemove).stream() - .peek(dp -> dp.setDefaultQueueId(newQueueId)) - .forEach(this::saveDeviceProfile); - } deleteQueue(tenantId, queueIdForRemove); }); }); } - //TODO: remove after implementing TbDeviceProfileService - private void saveDeviceProfile(DeviceProfile deviceProfile) { - DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); - tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); - tbClusterService.broadcastEntityStateChangeEvent(deviceProfile.getTenantId(), savedDeviceProfile.getId(), ComponentLifecycleEvent.UPDATED); - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java index 2a8fc1bf0a..01a2eee664 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/DefaultTbTenantService.java @@ -19,59 +19,58 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.queue.TbQueueService; import org.thingsboard.server.service.install.InstallScripts; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import java.util.Collections; +import java.util.concurrent.TimeUnit; @Service @TbCoreComponent @RequiredArgsConstructor public class DefaultTbTenantService extends AbstractTbEntityService implements TbTenantService { + private final TenantService tenantService; + private final TbTenantProfileCache tenantProfileCache; private final InstallScripts installScripts; private final TbQueueService tbQueueService; private final TenantProfileService tenantProfileService; + private final EntitiesVersionControlService versionControlService; @Override - public Tenant save(Tenant tenant) throws ThingsboardException { - try { - boolean created = tenant.getId() == null; - Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; + public Tenant save(Tenant tenant) throws Exception { + boolean created = tenant.getId() == null; + Tenant oldTenant = !created ? tenantService.findTenantById(tenant.getId()) : null; - Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant)); - if (created) { - installScripts.createDefaultRuleChains(savedTenant.getId()); - installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); - } - tenantProfileCache.evict(savedTenant.getId()); - notificationEntityService.notifyCreateOruUpdateTenant(savedTenant, created ? - ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - - TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; - TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); - tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile); - return savedTenant; - } catch (Exception e) { - throw handleException(e); + Tenant savedTenant = checkNotNull(tenantService.saveTenant(tenant)); + if (created) { + installScripts.createDefaultRuleChains(savedTenant.getId()); + installScripts.createDefaultEdgeRuleChains(savedTenant.getId()); } + tenantProfileCache.evict(savedTenant.getId()); + notificationEntityService.notifyCreateOrUpdateTenant(savedTenant, created ? + ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + + TenantProfile oldTenantProfile = oldTenant != null ? tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, oldTenant.getTenantProfileId()) : null; + TenantProfile newTenantProfile = tenantProfileService.findTenantProfileById(TenantId.SYS_TENANT_ID, savedTenant.getTenantProfileId()); + tbQueueService.updateQueuesByTenants(Collections.singletonList(savedTenant.getTenantId()), newTenantProfile, oldTenantProfile); + return savedTenant; } @Override - public void delete(Tenant tenant) throws ThingsboardException { - try { - TenantId tenantId = tenant.getId(); - tenantService.deleteTenant(tenantId); - tenantProfileCache.evict(tenantId); - notificationEntityService.notifyDeleteTenant(tenant); - } catch (Exception e) { - throw handleException(e); - } + public void delete(Tenant tenant) throws Exception { + TenantId tenantId = tenant.getId(); + tenantService.deleteTenant(tenantId); + tenantProfileCache.evict(tenantId); + notificationEntityService.notifyDeleteTenant(tenant); + versionControlService.deleteVersionControlSettings(tenantId).get(1, TimeUnit.MINUTES); } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java index c265b568cb..2dc8fa452c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/TbTenantService.java @@ -16,12 +16,11 @@ package org.thingsboard.server.service.entitiy.tenant; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.exception.ThingsboardException; public interface TbTenantService { - Tenant save(Tenant tenant) throws ThingsboardException; + Tenant save(Tenant tenant) throws Exception; - void delete(Tenant tenant) throws ThingsboardException; + void delete(Tenant tenant) throws Exception; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/DefaultTbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java similarity index 54% rename from application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/DefaultTbTenantProfileService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java index 23d9f74b8e..a25c4acad7 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant_profile/DefaultTbTenantProfileService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/DefaultTbTenantProfileService.java @@ -13,16 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.tenant_profile; +package org.thingsboard.server.service.entitiy.tenant.profile; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.entitiy.queue.TbQueueService; import java.util.List; @@ -31,20 +35,31 @@ import java.util.List; @Service @TbCoreComponent @AllArgsConstructor -public class DefaultTbTenantProfileService implements TbTenantProfileService { +public class DefaultTbTenantProfileService extends AbstractTbEntityService implements TbTenantProfileService { private final TbQueueService tbQueueService; private final TenantProfileService tenantProfileService; private final TenantService tenantService; + private final TbTenantProfileCache tenantProfileCache; @Override - public TenantProfile saveTenantProfile(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile) { - TenantProfile savedTenantProfile = tenantProfileService.saveTenantProfile(tenantId, tenantProfile); - + public TenantProfile save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile) throws ThingsboardException { + TenantProfile savedTenantProfile = checkNotNull(tenantProfileService.saveTenantProfile(tenantId, tenantProfile)); if (oldTenantProfile != null && savedTenantProfile.isIsolatedTbRuleEngine()) { List tenantIds = tenantService.findTenantIdsByTenantProfileId(savedTenantProfile.getId()); tbQueueService.updateQueuesByTenants(tenantIds, savedTenantProfile, oldTenantProfile); } + tenantProfileCache.put(savedTenantProfile); + tbClusterService.onTenantProfileChange(savedTenantProfile, null); + tbClusterService.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, savedTenantProfile.getId(), + tenantProfile.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + return savedTenantProfile; } + + @Override + public void delete(TenantId tenantId, TenantProfile tenantProfile) throws ThingsboardException { + tenantProfileService.deleteTenantProfile(tenantId, tenantProfile.getId()); + tbClusterService.onTenantProfileDelete(tenantProfile, null); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.java new file mode 100644 index 0000000000..96d18e3577 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/tenant/profile/TbTenantProfileService.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.service.entitiy.tenant.profile; + +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; + +public interface TbTenantProfileService { + TenantProfile save(TenantId tenantId, TenantProfile tenantProfile, TenantProfile oldTenantProfile) throws ThingsboardException; + + void delete(TenantId tenantId, TenantProfile tenantProfile) throws ThingsboardException; +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index 592496e6f8..a41bbb4385 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -28,9 +28,9 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; @@ -44,12 +44,13 @@ import static org.thingsboard.server.controller.UserController.ACTIVATE_URL_PATT @Slf4j public class DefaultUserService extends AbstractTbEntityService implements TbUserService { + private final UserService userService; private final MailService mailService; private final SystemSecurityService systemSecurityService; @Override public User save(TenantId tenantId, CustomerId customerId, User tbUser, boolean sendActivationMail, - HttpServletRequest request, SecurityUser user) throws ThingsboardException { + HttpServletRequest request, User user) throws ThingsboardException { ActionType actionType = tbUser.getId() == null ? ActionType.ADDED : ActionType.UPDATED; try { boolean sendEmail = tbUser.getId() == null && sendActivationMail; @@ -71,25 +72,24 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse savedUser, user, actionType, true, null); return savedUser; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.USER), - tbUser, user, actionType, false, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.USER), tbUser, actionType, user, e); + throw e; } } @Override - public void delete(TenantId tenantId, CustomerId customerId, User tbUser, SecurityUser user) throws ThingsboardException { + public void delete(TenantId tenantId, CustomerId customerId, User tbUser, User user) throws ThingsboardException { UserId userId = tbUser.getId(); + try { List relatedEdgeIds = findRelatedEdgeIds(tenantId, userId); - userService.deleteUser(tenantId, userId); notificationEntityService.notifyDeleteEntity(tenantId, userId, tbUser, customerId, - ActionType.DELETED, relatedEdgeIds, user, userId.toString()); + ActionType.DELETED, relatedEdgeIds, user, userId.toString()); } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.USER), - null, user, ActionType.DELETED, false, e, userId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.USER), + ActionType.DELETED, user, e, userId.toString()); + throw e; } } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java index 177aad17cc..765733ba3e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/TbUserService.java @@ -19,12 +19,11 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.security.model.SecurityUser; import javax.servlet.http.HttpServletRequest; public interface TbUserService { - User save(TenantId tenantId, CustomerId customerId, User tbUser, boolean sendActivationMail, HttpServletRequest request, SecurityUser user) throws ThingsboardException; + User save(TenantId tenantId, CustomerId customerId, User tbUser, boolean sendActivationMail, HttpServletRequest request, User user) throws ThingsboardException; - void delete (TenantId tenantId, CustomerId customerId, User tbUser, SecurityUser user) throws ThingsboardException; + void delete(TenantId tenantId, CustomerId customerId, User tbUser, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgetsBundle/DefaultWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java similarity index 55% rename from application/src/main/java/org/thingsboard/server/service/entitiy/widgetsBundle/DefaultWidgetsBundleService.java rename to application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java index f3d8dfc138..1706598b9c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgetsBundle/DefaultWidgetsBundleService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/DefaultWidgetsBundleService.java @@ -13,41 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.widgetsBundle; +package org.thingsboard.server.service.entitiy.widgets.bundle; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; @Service @TbCoreComponent @AllArgsConstructor -public class DefaultWidgetsBundleService extends AbstractTbEntityService implements TbWidgetsBundleService{ +public class DefaultWidgetsBundleService extends AbstractTbEntityService implements TbWidgetsBundleService { + + private final WidgetsBundleService widgetsBundleService; + @Override - public WidgetsBundle save(WidgetsBundle widgetsBundle, SecurityUser user) throws ThingsboardException { - try { + public WidgetsBundle save(WidgetsBundle widgetsBundle, User user) throws Exception { WidgetsBundle savedWidgetsBundle = checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle)); - notificationEntityService.notifySendMsgToEdgeService(widgetsBundle.getTenantId(), savedWidgetsBundle.getId(), - widgetsBundle.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); + autoCommit(user, savedWidgetsBundle.getId()); + notificationEntityService.notifySendMsgToEdgeService(widgetsBundle.getTenantId(), savedWidgetsBundle.getId(), + widgetsBundle.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); return savedWidgetsBundle; - } catch (Exception e) { - throw handleException(e); - } } @Override - public void delete(WidgetsBundle widgetsBundle, SecurityUser user) throws ThingsboardException { - try { - widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getTenantId(), widgetsBundle.getId()); - notificationEntityService.notifySendMsgToEdgeService(widgetsBundle.getTenantId(), widgetsBundle.getId(), - EdgeEventActionType.DELETED); - } catch (Exception e) { - throw handleException(e); - } + public void delete(WidgetsBundle widgetsBundle) throws ThingsboardException { + widgetsBundleService.deleteWidgetsBundle(widgetsBundle.getTenantId(), widgetsBundle.getId()); + notificationEntityService.notifySendMsgToEdgeService(widgetsBundle.getTenantId(), widgetsBundle.getId(), + EdgeEventActionType.DELETED); } } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java new file mode 100644 index 0000000000..2820934aa3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/widgets/bundle/TbWidgetsBundleService.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy.widgets.bundle; + +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.widget.WidgetsBundle; + +public interface TbWidgetsBundleService { + + WidgetsBundle save(WidgetsBundle entity, User currentUser) throws Exception; + + void delete(WidgetsBundle entity) throws ThingsboardException; +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index cece3377b0..78e03ffe1a 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; @@ -162,6 +163,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Getter private boolean persistActivityToTelemetry; + @Lazy @Autowired private QueueService queueService; @@ -193,22 +195,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { public void createDefaultTenantProfiles() throws Exception { tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID); - TenantProfileData tenantProfileData = new TenantProfileData(); - tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); - - TenantProfile isolatedTbCoreProfile = new TenantProfile(); - isolatedTbCoreProfile.setDefault(false); - isolatedTbCoreProfile.setName("Isolated TB Core"); - isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile"); - isolatedTbCoreProfile.setIsolatedTbCore(true); - isolatedTbCoreProfile.setIsolatedTbRuleEngine(false); - isolatedTbCoreProfile.setProfileData(tenantProfileData); - try { - tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreProfile); - } catch (DataValidationException e) { - log.warn(e.getMessage()); - } - TenantProfileData isolatedRuleEngineTenantProfileData = new TenantProfileData(); isolatedRuleEngineTenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); @@ -237,7 +223,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedTbRuleEngineProfile.setDefault(false); isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine"); isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile"); - isolatedTbRuleEngineProfile.setIsolatedTbCore(false); isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true); isolatedTbRuleEngineProfile.setProfileData(isolatedRuleEngineTenantProfileData); @@ -246,25 +231,12 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } catch (DataValidationException e) { log.warn(e.getMessage()); } - - TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile(); - isolatedTbCoreAndTbRuleEngineProfile.setDefault(false); - isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine"); - isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile"); - isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true); - isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true); - isolatedTbCoreAndTbRuleEngineProfile.setProfileData(isolatedRuleEngineTenantProfileData); - - try { - tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreAndTbRuleEngineProfile); - } catch (DataValidationException e) { - log.warn(e.getMessage()); - } } @Override public void createAdminSettings() throws Exception { AdminSettings generalSettings = new AdminSettings(); + generalSettings.setTenantId(TenantId.SYS_TENANT_ID); generalSettings.setKey("general"); ObjectNode node = objectMapper.createObjectNode(); node.put("baseUrl", "http://localhost:8080"); @@ -273,6 +245,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings); AdminSettings mailSettings = new AdminSettings(); + mailSettings.setTenantId(TenantId.SYS_TENANT_ID); mailSettings.setKey("mail"); node = objectMapper.createObjectNode(); node.put("mailFrom", "ThingsBoard "); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 306102c66c..d22039683d 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntitySubtype; @@ -121,6 +122,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @Autowired private ApiUsageStateService apiUsageStateService; + @Lazy @Autowired private QueueService queueService; @@ -587,10 +589,6 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } catch (Exception e) { } - log.info("Updating device profiles..."); - schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.4", "schema_update_device_profile.sql"); - loadSql(schemaUpdateFile, conn); - log.info("Updating schema settings..."); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004000;"); log.info("Schema updated."); diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index 7a5bc08236..9af49f4a7c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @@ -82,13 +83,21 @@ public class DefaultCacheCleanupService implements CacheCleanupService { } void clearCacheByName(final String cacheName) { + log.info("Clearing cache [{}]", cacheName); Cache cache = cacheManager.getCache(cacheName); Objects.requireNonNull(cache, "Cache does not exist for name " + cacheName); cache.clear(); } void clearAll() { - redisTemplate.ifPresent(rt -> rt.execute(connection -> - connection.execute("FLUSHALL"), false)); + if (redisTemplate.isPresent()) { + log.info("Flushing all caches"); + redisTemplate.get().execute((RedisCallback) connection -> { + connection.flushAll(); + return null; + }); + return; + } + cacheManager.getCacheNames().forEach(this::clearCacheByName); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index e27f2e88c2..cb560bb5ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -21,8 +21,8 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; @@ -56,12 +56,9 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.alarm.AlarmDao; -import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; -import org.thingsboard.server.dao.model.sql.RelationEntity; -import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -69,7 +66,6 @@ import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.SystemDataLoaderService; import org.thingsboard.server.service.install.TbRuleEngineQueueConfigService; @@ -107,9 +103,6 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired private TimeseriesService tsService; - @Autowired - private AlarmService alarmService; - @Autowired private EntityService entityService; @@ -120,11 +113,12 @@ public class DefaultDataUpdateService implements DataUpdateService { private DeviceProfileRepository deviceProfileRepository; @Autowired - private OAuth2Service oAuth2Service; + private RateLimitsUpdater rateLimitsUpdater; @Autowired private TenantProfileService tenantProfileService; + @Lazy @Autowired private QueueService queueService; @@ -162,8 +156,8 @@ public class DefaultDataUpdateService implements DataUpdateService { break; case "3.3.4": log.info("Updating data from version 3.3.4 to 3.4.0 ..."); - tenantsProfileQueueConfigurationUpdater.updateEntities(null); - checkPointRuleNodesUpdater.updateEntities(null); + rateLimitsUpdater.updateEntities(); + tenantsProfileQueueConfigurationUpdater.updateEntities(); break; default: throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); @@ -634,47 +628,4 @@ public class DefaultDataUpdateService implements DataUpdateService { return mainQueueConfiguration; } - private final PaginatedUpdater checkPointRuleNodesUpdater = - new PaginatedUpdater<>() { - - @Override - protected String getName() { - return "Checkpoint rule nodes updater"; - } - - @Override - protected boolean forceReportTotal() { - return true; - } - - @Override - protected PageData findEntities(String id, PageLink pageLink) { - return ruleChainService.findAllRuleNodesByType("org.thingsboard.rule.engine.flow.TbCheckpointNode", pageLink); - } - - @Override - protected void updateEntity(RuleNode ruleNode) { - updateCheckPointRuleNodeConfiguration(ruleNode); - } - }; - - private void updateCheckPointRuleNodeConfiguration(RuleNode node) { - try { - ObjectNode configuration = (ObjectNode) node.getConfiguration(); - JsonNode queueNameNode = configuration.remove("queueName"); - if (queueNameNode != null) { - RuleChain ruleChain = this.ruleChainService.findRuleChainById(TenantId.SYS_TENANT_ID, node.getRuleChainId()); - TenantId tenantId = ruleChain.getTenantId(); - Map queues = - queueService.findQueuesByTenantId(tenantId).stream().collect(Collectors.toMap(Queue::getName, Queue::getId)); - String queueName = queueNameNode.asText(); - QueueId queueId = queues.get(queueName); - configuration.put("queueId", queueId != null ? queueId.toString() : ""); - ruleChainService.saveRuleNode(tenantId, node); - } - } catch (Exception e) { - log.error("Failed to update checkpoint rule node configuration name=["+node.getName()+"], id=["+ node.getId().getId() +"]", e); - } - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java index 914a136a4c..b2fb707791 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java @@ -49,6 +49,10 @@ public abstract class PaginatedUpdater { } } + public void updateEntities() { + updateEntities(null); + } + protected boolean forceReportTotal() { return false; } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java new file mode 100644 index 0000000000..c136f97b52 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/update/RateLimitsUpdater.java @@ -0,0 +1,115 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.install.update; + +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.TenantProfile; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.tenant.TenantProfileService; + +@Component +class RateLimitsUpdater extends PaginatedUpdater { + + @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_TENANT_ENABLED') ?: environment.getProperty('server.rest.limits.tenant.enabled') ?: 'false' }") + boolean tenantServerRestLimitsEnabled; + @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_TENANT_CONFIGURATION') ?: environment.getProperty('server.rest.limits.tenant.configuration') ?: '100:1,2000:60' }") + String tenantServerRestLimitsConfiguration; + @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED') ?: environment.getProperty('server.rest.limits.customer.enabled') ?: 'false' }") + boolean customerServerRestLimitsEnabled; + @Value("#{ environment.getProperty('TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION') ?: environment.getProperty('server.rest.limits.customer.configuration') ?: '50:1,1000:60' }") + String customerServerRestLimitsConfiguration; + + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_TENANT') ?: environment.getProperty('server.ws.limits.max_sessions_per_tenant') ?: '0' }") + private int maxWsSessionsPerTenant; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_CUSTOMER') ?: environment.getProperty('server.ws.limits.max_sessions_per_customer') ?: '0' }") + private int maxWsSessionsPerCustomer; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_REGULAR_USER') ?: environment.getProperty('server.ws.limits.max_sessions_per_regular_user') ?: '0' }") + private int maxWsSessionsPerRegularUser; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_PUBLIC_USER') ?: environment.getProperty('server.ws.limits.max_sessions_per_public_user') ?: '0' }") + private int maxWsSessionsPerPublicUser; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_QUEUE_PER_WS_SESSION') ?: environment.getProperty('server.ws.limits.max_queue_per_ws_session') ?: '500' }") + private int wsMsgQueueLimitPerSession; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_TENANT') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_tenant') ?: '0' }") + private long maxWsSubscriptionsPerTenant; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_CUSTOMER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_customer') ?: '0' }") + private long maxWsSubscriptionsPerCustomer; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_REGULAR_USER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_regular_user') ?: '0' }") + private long maxWsSubscriptionsPerRegularUser; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_PUBLIC_USER') ?: environment.getProperty('server.ws.limits.max_subscriptions_per_public_user') ?: '0' }") + private long maxWsSubscriptionsPerPublicUser; + @Value("#{ environment.getProperty('TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_UPDATES_PER_SESSION') ?: environment.getProperty('server.ws.limits.max_updates_per_session') ?: '300:1,3000:60' }") + private String wsUpdatesPerSessionRateLimit; + + @Value("#{ environment.getProperty('CASSANDRA_QUERY_TENANT_RATE_LIMITS_ENABLED') ?: environment.getProperty('cassandra.query.tenant_rate_limits.enabled') ?: 'false' }") + private boolean cassandraQueryTenantRateLimitsEnabled; + @Value("#{ environment.getProperty('CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION') ?: environment.getProperty('cassandra.query.tenant_rate_limits.configuration') ?: '1000:1,30000:60' }") + private String cassandraQueryTenantRateLimitsConfiguration; + + @Autowired + private TenantProfileService tenantProfileService; + + @Override + protected boolean forceReportTotal() { + return true; + } + + @Override + protected String getName() { + return "Rate limits updater"; + } + + @Override + protected PageData findEntities(String id, PageLink pageLink) { + return tenantProfileService.findTenantProfiles(TenantId.SYS_TENANT_ID, pageLink); + } + + @Override + protected void updateEntity(TenantProfile tenantProfile) { + var profileConfiguration = tenantProfile.getDefaultProfileConfiguration(); + + if (tenantServerRestLimitsEnabled && StringUtils.isNotEmpty(tenantServerRestLimitsConfiguration)) { + profileConfiguration.setTenantServerRestLimitsConfiguration(tenantServerRestLimitsConfiguration); + } + if (customerServerRestLimitsEnabled && StringUtils.isNotEmpty(customerServerRestLimitsConfiguration)) { + profileConfiguration.setCustomerServerRestLimitsConfiguration(customerServerRestLimitsConfiguration); + } + + profileConfiguration.setMaxWsSessionsPerTenant(maxWsSessionsPerTenant); + profileConfiguration.setMaxWsSessionsPerCustomer(maxWsSessionsPerCustomer); + profileConfiguration.setMaxWsSessionsPerPublicUser(maxWsSessionsPerPublicUser); + profileConfiguration.setMaxWsSessionsPerRegularUser(maxWsSessionsPerRegularUser); + profileConfiguration.setMaxWsSubscriptionsPerTenant(maxWsSubscriptionsPerTenant); + profileConfiguration.setMaxWsSubscriptionsPerCustomer(maxWsSubscriptionsPerCustomer); + profileConfiguration.setMaxWsSubscriptionsPerPublicUser(maxWsSubscriptionsPerPublicUser); + profileConfiguration.setMaxWsSubscriptionsPerRegularUser(maxWsSubscriptionsPerRegularUser); + profileConfiguration.setWsMsgQueueLimitPerSession(wsMsgQueueLimitPerSession); + if (StringUtils.isNotEmpty(wsUpdatesPerSessionRateLimit)) { + profileConfiguration.setWsUpdatesPerSessionRateLimit(wsUpdatesPerSessionRateLimit); + } + + if (cassandraQueryTenantRateLimitsEnabled && StringUtils.isNotEmpty(cassandraQueryTenantRateLimitsConfiguration)) { + profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(cassandraQueryTenantRateLimitsConfiguration); + } + + tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 7e1a3d7fa7..aa8e019317 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -20,7 +20,9 @@ import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Lazy; import org.springframework.core.NestedRuntimeException; @@ -47,13 +49,17 @@ import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; -import javax.mail.MessagingException; +import javax.annotation.PreDestroy; import javax.mail.internet.MimeMessage; import java.io.ByteArrayInputStream; import java.util.HashMap; import java.util.Locale; import java.util.Map; import java.util.Properties; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; @Service @Slf4j @@ -70,6 +76,8 @@ public class DefaultMailService implements MailService { private final AdminSettingsService adminSettingsService; private final TbApiUsageClient apiUsageClient; + private static final long DEFAULT_TIMEOUT = 10_000; + @Lazy @Autowired private TbApiUsageStateService apiUsageStateService; @@ -77,10 +85,15 @@ public class DefaultMailService implements MailService { @Autowired private MailExecutorService mailExecutorService; + @Autowired + private PasswordResetExecutorService passwordResetExecutorService; + private JavaMailSenderImpl mailSender; private String mailFrom; + private long timeout; + public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageClient apiUsageClient) { this.messages = messages; this.freemarkerConfig = freemarkerConfig; @@ -100,6 +113,7 @@ public class DefaultMailService implements MailService { JsonNode jsonConfig = settings.getJsonValue(); mailSender = createMailSender(jsonConfig); mailFrom = jsonConfig.get("mailFrom").asText(); + timeout = jsonConfig.get("timeout").asLong(DEFAULT_TIMEOUT); } else { throw new IncorrectParameterException("Failed to update mail configuration. Settings not found!"); } @@ -166,7 +180,7 @@ public class DefaultMailService implements MailService { @Override public void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException { - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override @@ -174,13 +188,14 @@ public class DefaultMailService implements MailService { JavaMailSenderImpl testMailSender = createMailSender(jsonConfig); String mailFrom = jsonConfig.get("mailFrom").asText(); String subject = messages.getMessage("test.message.subject", null, Locale.US); + long timeout = jsonConfig.get("timeout").asLong(DEFAULT_TIMEOUT); Map model = new HashMap<>(); model.put(TARGET_EMAIL, email); String message = mergeTemplateIntoString("test.ftl", model); - sendMail(testMailSender, mailFrom, email, subject, message); + sendMail(testMailSender, mailFrom, email, subject, message, timeout); } @Override @@ -194,7 +209,7 @@ public class DefaultMailService implements MailService { String message = mergeTemplateIntoString("activation.ftl", model); - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override @@ -208,7 +223,7 @@ public class DefaultMailService implements MailService { String message = mergeTemplateIntoString("account.activated.ftl", model); - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override @@ -222,12 +237,12 @@ public class DefaultMailService implements MailService { String message = mergeTemplateIntoString("reset.password.ftl", model); - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override public void sendResetPasswordEmailAsync(String passwordResetLink, String email) { - mailExecutorService.execute(() -> { + passwordResetExecutorService.execute(() -> { try { this.sendResetPasswordEmail(passwordResetLink, email); } catch (ThingsboardException e) { @@ -247,20 +262,20 @@ public class DefaultMailService implements MailService { String message = mergeTemplateIntoString("password.was.reset.ftl", model); - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override public void send(TenantId tenantId, CustomerId customerId, TbEmail tbEmail) throws ThingsboardException { - sendMail(tenantId, customerId, tbEmail, this.mailSender); + sendMail(tenantId, customerId, tbEmail, this.mailSender, timeout); } @Override - public void send(TenantId tenantId, CustomerId customerId, TbEmail tbEmail, JavaMailSender javaMailSender) throws ThingsboardException { - sendMail(tenantId, customerId, tbEmail, javaMailSender); + public void send(TenantId tenantId, CustomerId customerId, TbEmail tbEmail, JavaMailSender javaMailSender, long timeout) throws ThingsboardException { + sendMail(tenantId, customerId, tbEmail, javaMailSender, timeout); } - private void sendMail(TenantId tenantId, CustomerId customerId, TbEmail tbEmail, JavaMailSender javaMailSender) throws ThingsboardException { + private void sendMail(TenantId tenantId, CustomerId customerId, TbEmail tbEmail, JavaMailSender javaMailSender, long timeout) throws ThingsboardException { if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { try { MimeMessage mailMsg = javaMailSender.createMimeMessage(); @@ -287,7 +302,7 @@ public class DefaultMailService implements MailService { helper.addInline(imgId, iss, contentType); } } - javaMailSender.send(helper.getMimeMessage()); + sendMailWithTimeout(javaMailSender, helper.getMimeMessage(), timeout); apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } catch (Exception e) { throw handleException(e); @@ -308,7 +323,7 @@ public class DefaultMailService implements MailService { String message = mergeTemplateIntoString("account.lockout.ftl", model); - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override @@ -320,7 +335,7 @@ public class DefaultMailService implements MailService { "expirationTimeSeconds", expirationTimeSeconds )); - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override @@ -347,7 +362,7 @@ public class DefaultMailService implements MailService { message = mergeTemplateIntoString("state.disabled.ftl", model); break; } - sendMail(mailSender, mailFrom, email, subject, message); + sendMail(mailSender, mailFrom, email, subject, message, timeout); } @Override @@ -447,9 +462,8 @@ public class DefaultMailService implements MailService { } } - private void sendMail(JavaMailSenderImpl mailSender, - String mailFrom, String email, - String subject, String message) throws ThingsboardException { + private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, + String subject, String message, long timeout) throws ThingsboardException { try { MimeMessage mimeMsg = mailSender.createMimeMessage(); MimeMessageHelper helper = new MimeMessageHelper(mimeMsg, UTF_8); @@ -457,12 +471,24 @@ public class DefaultMailService implements MailService { helper.setTo(email); helper.setSubject(subject); helper.setText(message, true); - mailSender.send(helper.getMimeMessage()); + + sendMailWithTimeout(mailSender, helper.getMimeMessage(), timeout); } catch (Exception e) { throw handleException(e); } } + private void sendMailWithTimeout(JavaMailSender mailSender, MimeMessage msg, long timeout) { + try { + mailExecutorService.submit(() -> mailSender.send(msg)).get(timeout, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + log.debug("Error during mail submission", e); + throw new RuntimeException("Timeout!"); + } catch (Exception e) { + throw new RuntimeException(ExceptionUtils.getRootCause(e)); + } + } + private String mergeTemplateIntoString(String templateLocation, Map model) throws ThingsboardException { try { diff --git a/application/src/main/java/org/thingsboard/server/service/mail/PasswordResetExecutorService.java b/application/src/main/java/org/thingsboard/server/service/mail/PasswordResetExecutorService.java new file mode 100644 index 0000000000..ea761c144d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/mail/PasswordResetExecutorService.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.mail; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class PasswordResetExecutorService extends AbstractListeningExecutor { + + @Value("${actors.rule.mail_password_reset_thread_pool_size:10}") + private int mailExecutorThreadPoolSize; + + @Override + protected int getThreadPollSize() { + return mailExecutorThreadPoolSize; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 2806c35cda..062e4128d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -53,7 +53,7 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; @@ -135,6 +135,15 @@ public class DefaultTbClusterService implements TbClusterService { toCoreMsgs.incrementAndGet(); } + @Override + public void pushMsgToVersionControl(TenantId tenantId, TransportProtos.ToVersionControlServiceMsg msg, TbQueueCallback callback) { + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_VC_EXECUTOR, tenantId, tenantId); + log.trace("PUSHING msg: {} to:{}", msg, tpi); + producerProvider.getTbVersionControlMsgProducer().send(tpi, new TbProtoQueueMsg<>(tenantId.getId(), msg), callback); + //TODO: ashvayka + toCoreMsgs.incrementAndGet(); + } + @Override public void pushNotificationToCore(String serviceId, FromDeviceRpcResponse response, TbQueueCallback callback) { TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId); @@ -172,7 +181,7 @@ public class DefaultTbClusterService implements TbClusterService { tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId()))); } } - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueId(), tenantId, entityId); + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId); log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) @@ -185,16 +194,16 @@ public class DefaultTbClusterService implements TbClusterService { private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) { if (deviceProfile != null) { RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId(); - QueueId targetQueueId = deviceProfile.getDefaultQueueId(); + String targetQueueName = deviceProfile.getDefaultQueueName(); boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId()); - boolean isQueueTransform = targetQueueId != null && !targetQueueId.equals(tbMsg.getQueueId()); + boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); if (isRuleChainTransform && isQueueTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueId); + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); } else if (isRuleChainTransform) { tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); } else if (isQueueTransform) { - tbMsg = TbMsg.transformMsg(tbMsg, targetQueueId); + tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName); } } return tbMsg; @@ -355,13 +364,14 @@ public class DefaultTbClusterService implements TbClusterService { private void broadcast(ComponentLifecycleMsg msg) { byte[] msgBytes = encodingService.encode(msg); TbQueueProducer> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); - Set tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); + Set tbRuleEngineServices = partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE); EntityType entityType = msg.getEntityId().getEntityType(); if (entityType.equals(EntityType.TENANT) || entityType.equals(EntityType.TENANT_PROFILE) || entityType.equals(EntityType.DEVICE_PROFILE) || entityType.equals(EntityType.API_USAGE_STATE) || (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED) + || entityType.equals(EntityType.ENTITY_VIEW) || entityType.equals(EntityType.EDGE)) { TbQueueProducer> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); Set tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); @@ -432,12 +442,12 @@ public class DefaultTbClusterService implements TbClusterService { sendDeviceStateServiceEvent(device.getTenantId(), device.getId(), created, !created, false); otaPackageStateService.update(device, old); if (!created && notifyEdge) { - sendNotificationMsgToEdgeService(device.getTenantId(), null, device.getId(), null, null, EdgeEventActionType.UPDATED); + sendNotificationMsgToEdge(device.getTenantId(), null, device.getId(), null, null, EdgeEventActionType.UPDATED); } } @Override - public void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { + public void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) { if (!edgesEnabled) { return; } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 2ca47d689f..a3bae8bc4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -28,6 +28,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.server.common.msg.MsgType; @@ -36,7 +37,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.stats.StatsFactory; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; @@ -75,6 +76,8 @@ import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; +import org.thingsboard.server.service.sync.vc.GitVersionControlQueueService; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import javax.annotation.PostConstruct; @@ -117,6 +120,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> usageStatsConsumer; private final TbQueueConsumer> firmwareStatesConsumer; @@ -138,7 +142,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); if (actorMsg.isPresent()) { @@ -322,6 +332,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> pending : map.entrySet()) { ToRuleEngineMsg tmp = pending.getValue().getValue(); - TbMsg tmpMsg = TbMsg.fromBytes(configuration.getId(), tmp.getTbMsg().toByteArray(), TbMsgCallback.EMPTY); + TbMsg tmpMsg = TbMsg.fromBytes(configuration.getName(), tmp.getTbMsg().toByteArray(), TbMsgCallback.EMPTY); RuleNodeInfo ruleNodeInfo = ctx.getLastVisitedRuleNode(pending.getKey()); if (printAll) { log.trace("[{}] {} to process message: {}, Last Rule Node: {}", TenantId.fromUUID(new UUID(tmp.getTenantIdMSB(), tmp.getTenantIdLSB())), prefix, tmpMsg, ruleNodeInfo); @@ -462,8 +461,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< partitionService.removeQueue(queueDeleteMsg); } - private void forwardToRuleEngineActor(QueueId queueId, TenantId tenantId, ToRuleEngineMsg toRuleEngineMsg, TbMsgCallback callback) { - TbMsg tbMsg = TbMsg.fromBytes(queueId, toRuleEngineMsg.getTbMsg().toByteArray(), callback); + private void forwardToRuleEngineActor(String queueName, TenantId tenantId, ToRuleEngineMsg toRuleEngineMsg, TbMsgCallback callback) { + TbMsg tbMsg = TbMsg.fromBytes(queueName, toRuleEngineMsg.getTbMsg().toByteArray(), callback); QueueToRuleEngineMsg msg; ProtocolStringList relationTypesList = toRuleEngineMsg.getRelationTypesList(); Set relationTypes = null; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java index 75fbd39a95..1ab9686d6f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java @@ -45,7 +45,7 @@ public class DefaultTenantRoutingInfoService implements TenantRoutingInfoService Tenant tenant = tenantService.findTenantById(tenantId); if (tenant != null) { TenantProfile tenantProfile = tenantProfileCache.get(tenant.getTenantProfileId()); - return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbCore(), tenantProfile.isIsolatedTbRuleEngine()); + return new TenantRoutingInfo(tenantId, tenantProfile.isIsolatedTbRuleEngine()); } else { throw new RuntimeException("Tenant not found!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java index 2432bca426..11740aeb79 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java @@ -38,6 +38,7 @@ public class TbCoreConsumerStats { public static final String SUBSCRIPTION_MSGS = "subMsgs"; public static final String TO_CORE_NOTIFICATIONS = "coreNfs"; public static final String EDGE_NOTIFICATIONS = "edgeNfs"; + public static final String DEVICE_ACTIVITIES = "deviceActivity"; private final StatsCounter totalCounter; private final StatsCounter sessionEventCounter; @@ -52,6 +53,7 @@ public class TbCoreConsumerStats { private final StatsCounter subscriptionMsgCounter; private final StatsCounter toCoreNotificationsCounter; private final StatsCounter edgeNotificationsCounter; + private final StatsCounter deviceActivitiesCounter; private final List counters = new ArrayList<>(); @@ -70,6 +72,7 @@ public class TbCoreConsumerStats { this.subscriptionMsgCounter = register(statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS)); this.toCoreNotificationsCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NOTIFICATIONS)); this.edgeNotificationsCounter = register(statsFactory.createStatsCounter(statsKey, EDGE_NOTIFICATIONS)); + this.deviceActivitiesCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_ACTIVITIES)); } private StatsCounter register(StatsCounter counter){ @@ -112,6 +115,11 @@ public class TbCoreConsumerStats { edgeNotificationsCounter.increment(); } + public void log(TransportProtos.DeviceActivityProto msg) { + totalCounter.increment(); + deviceActivitiesCounter.increment(); + } + public void log(TransportProtos.SubscriptionMgrMsgProto msg) { totalCounter.increment(); subscriptionMsgCounter.increment(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 997674753c..98fb95ad15 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -37,7 +37,7 @@ import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; @@ -160,7 +160,7 @@ public abstract class AbstractConsumerService log.trace("Going to reprocess [{}]: {}", id, TbMsg.fromBytes(result.getQueueId(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); + toReprocess.forEach((id, msg) -> log.trace("Going to reprocess [{}]: {}", id, TbMsg.fromBytes(result.getQueueName(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); } if (pauseBetweenRetries > 0) { try { @@ -164,10 +164,10 @@ public class TbRuleEngineProcessingStrategyFactory { log.debug("[{}] Reprocessing skipped for {} failed and {} timeout messages", queueName, result.getFailedMap().size(), result.getPendingMap().size()); } if (log.isTraceEnabled()) { - result.getFailedMap().forEach((id, msg) -> log.trace("Failed messages [{}]: {}", id, TbMsg.fromBytes(result.getQueueId(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); + result.getFailedMap().forEach((id, msg) -> log.trace("Failed messages [{}]: {}", id, TbMsg.fromBytes(result.getQueueName(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); } if (log.isTraceEnabled()) { - result.getPendingMap().forEach((id, msg) -> log.trace("Timeout messages [{}]: {}", id, TbMsg.fromBytes(result.getQueueId(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); + result.getPendingMap().forEach((id, msg) -> log.trace("Timeout messages [{}]: {}", id, TbMsg.fromBytes(result.getQueueName(), msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); } return new TbRuleEngineProcessingDecision(true, null); } diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index b6ce83a4f4..d83508220b 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; @@ -40,7 +41,6 @@ import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -69,45 +69,6 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); } - @Override - public TbResource saveResourceInternal(TbResource resource) throws ThingsboardException { - log.trace("Executing saveResource [{}]", resource); - if (StringUtils.isEmpty(resource.getData())) { - throw new DataValidationException("Resource data should be specified!"); - } - if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { - try { - List objectModels = - ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); - if (!objectModels.isEmpty()) { - ObjectModel objectModel = objectModels.get(0); - - String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.version; - String name = objectModel.name; - resource.setResourceKey(resourceKey); - if (resource.getId() == null) { - resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.version); - } - resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); - } else { - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); - } - } catch (InvalidDDFFileException e) { - log.error("Failed to parse file {}", resource.getFileName(), e); - throw new DataValidationException("Failed to parse file " + resource.getFileName()); - } catch (IOException e) { - throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); - } - if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) { - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); - } - } else { - resource.setResourceKey(resource.getFileName()); - } - - return resourceService.saveResource(resource); - } - @Override public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) { return resourceService.getResource(tenantId, resourceType, resourceId); @@ -218,35 +179,71 @@ public class DefaultTbResourceService extends AbstractTbEntityService implements } @Override - public TbResource save(TbResource tbResource, SecurityUser user) throws ThingsboardException { + public TbResource save(TbResource tbResource, User user) throws ThingsboardException { ActionType actionType = tbResource.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = tbResource.getTenantId(); try { - - TbResource savedResource = checkNotNull(saveResourceInternal(tbResource)); + TbResource savedResource = checkNotNull(doSave(tbResource)); tbClusterService.onResourceChange(savedResource, null); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedResource.getId(), - savedResource, user, actionType, false, null); + notificationEntityService.logEntityAction(tenantId, savedResource.getId(), savedResource, actionType, user); return savedResource; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.TB_RESOURCE), - tbResource, user, actionType, false, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), + tbResource, actionType, user, e); + throw e; } } @Override - public void delete(TbResource tbResource, SecurityUser user) throws ThingsboardException { + public void delete(TbResource tbResource, User user) { TbResourceId resourceId = tbResource.getId(); TenantId tenantId = tbResource.getTenantId(); try { resourceService.deleteResource(tenantId, resourceId); tbClusterService.onResourceDeleted(tbResource, null); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, resourceId, tbResource, user, ActionType.DELETED, - false, null, resourceId.toString()); + notificationEntityService.logEntityAction(tenantId, resourceId, tbResource, ActionType.DELETED, user, resourceId.toString()); } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.TB_RESOURCE), null, user, ActionType.DELETED, - false, e, resourceId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.TB_RESOURCE), + ActionType.DELETED, user, e, resourceId.toString()); + throw e; } - }} + } + + private TbResource doSave(TbResource resource) throws ThingsboardException { + log.trace("Executing saveResource [{}]", resource); + if (StringUtils.isEmpty(resource.getData())) { + throw new DataValidationException("Resource data should be specified!"); + } + if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { + try { + List objectModels = + ddfFileParser.parse(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); + if (!objectModels.isEmpty()) { + ObjectModel objectModel = objectModels.get(0); + + String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.version; + String name = objectModel.name; + resource.setResourceKey(resourceKey); + if (resource.getId() == null) { + resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.version); + } + resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); + } else { + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); + } + } catch (InvalidDDFFileException e) { + log.error("Failed to parse file {}", resource.getFileName(), e); + throw new DataValidationException("Failed to parse file " + resource.getFileName()); + } catch (IOException e) { + throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); + } + if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) { + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); + } + } else { + resource.setResourceKey(resource.getFileName()); + } + + return resourceService.saveResource(resource); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 3bcb7f0722..6fd95660fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.resource; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; @@ -30,8 +29,6 @@ import java.util.List; public interface TbResourceService extends SimpleTbEntityService { - TbResource saveResourceInternal(TbResource resource) throws ThingsboardException; - TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey); TbResource findResourceById(TenantId tenantId, TbResourceId resourceId); diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index ac82f78ccb..5bf7d7415c 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -23,6 +23,7 @@ import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.flow.TbRuleChainOutputNode; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -45,8 +46,10 @@ import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.install.InstallScripts; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; +import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -55,6 +58,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import java.util.UUID; import java.util.stream.Collectors; @RequiredArgsConstructor @@ -65,6 +69,9 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement private final RuleChainService ruleChainService; private final RelationService relationService; + private final InstallScripts installScripts; + + private final EntitiesVersionControlService vcService; @Override public Set getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId) { @@ -164,11 +171,12 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement } @Override - public RuleChain save(RuleChain ruleChain, SecurityUser user) throws ThingsboardException { + public RuleChain save(RuleChain ruleChain, User user) throws Exception { TenantId tenantId = ruleChain.getTenantId(); ActionType actionType = ruleChain.getId() == null ? ActionType.ADDED : ActionType.UPDATED; try { RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); + autoCommit(user, savedRuleChain.getId()); if (RuleChainType.CORE.equals(savedRuleChain.getType())) { tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), @@ -179,14 +187,13 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement savedRuleChain, user, actionType, sendMsgToEdge, null); return savedRuleChain; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - ruleChain, user, actionType, false, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ruleChain, actionType, user, e); + throw e; } } @Override - public void delete(RuleChain ruleChain, SecurityUser user) throws ThingsboardException { + public void delete(RuleChain ruleChain, User user) { TenantId tenantId = ruleChain.getTenantId(); RuleChainId ruleChainId = ruleChain.getId(); try { @@ -212,31 +219,31 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement notificationEntityService.notifyDeleteRuleChain(tenantId, ruleChain, relatedEdgeIds, user); } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - null, user, ActionType.DELETED, false, e, ruleChainId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.DELETED, + user, e, ruleChainId.toString()); + throw e; } } @Override - public RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, SecurityUser user) throws ThingsboardException { + public RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, User user) throws Exception { try { RuleChain savedRuleChain = installScripts.createDefaultRuleChain(tenantId, request.getName()); + autoCommit(user, savedRuleChain.getId()); tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), ComponentLifecycleEvent.CREATED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedRuleChain.getId(), - savedRuleChain, user, ActionType.ADDED, false, null); + notificationEntityService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, ActionType.ADDED, user); return savedRuleChain; } catch (Exception e) { RuleChain ruleChain = new RuleChain(); ruleChain.setName(request.getName()); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - ruleChain, user, ActionType.ADDED, false, e); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ruleChain, + ActionType.ADDED, user, e); + throw e; } } @Override - public RuleChain setRootRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException { + public RuleChain setRootRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException { RuleChainId ruleChainId = ruleChain.getId(); try { RuleChain previousRootRuleChain = ruleChainService.getRootTenantRuleChain(tenantId); @@ -247,27 +254,26 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement tbClusterService.broadcastEntityStateChangeEvent(tenantId, previousRootRuleChainId, ComponentLifecycleEvent.UPDATED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, previousRootRuleChainId, - previousRootRuleChain, user, ActionType.UPDATED, false, null); + notificationEntityService.logEntityAction(tenantId, previousRootRuleChainId, previousRootRuleChain, + ActionType.UPDATED, user); } ruleChain = ruleChainService.findRuleChainById(tenantId, ruleChainId); tbClusterService.broadcastEntityStateChangeEvent(tenantId, ruleChainId, ComponentLifecycleEvent.UPDATED); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, ruleChainId, - ruleChain, user, ActionType.UPDATED, false, null); + notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); } return ruleChain; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - ruleChain, user, ActionType.UPDATED, false, e, ruleChainId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + user, e, ruleChainId.toString()); + throw e; } } @Override public RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChain ruleChain, RuleChainMetaData ruleChainMetaData, - boolean updateRelated, SecurityUser user) throws ThingsboardException { + boolean updateRelated, User user) throws Exception { RuleChainId ruleChainId = ruleChain.getId(); RuleChainId ruleChainMetaDataId = ruleChainMetaData.getRuleChainId(); try { @@ -276,11 +282,20 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement List updatedRuleChains; if (updateRelated && result.isSuccess()) { - updatedRuleChains = tbRuleChainService.updateRelatedRuleChains(tenantId, ruleChainMetaDataId, result); + updatedRuleChains = updateRelatedRuleChains(tenantId, ruleChainMetaDataId, result); } else { updatedRuleChains = Collections.emptyList(); } + if (updatedRuleChains.isEmpty()) { + autoCommit(user, ruleChainMetaData.getRuleChainId()); + } else { + List uuids = new ArrayList<>(updatedRuleChains.size() + 1); + uuids.add(ruleChainMetaData.getRuleChainId().getId()); + updatedRuleChains.forEach(rc -> uuids.add(rc.getId().getId())); + autoCommit(user, EntityType.RULE_CHAIN, uuids); + } + RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaDataId)); if (RuleChainType.CORE.equals(ruleChain.getType())) { @@ -290,8 +305,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement }); } - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, ruleChainId, - ruleChain, user, ActionType.UPDATED, false, null, ruleChainMetaData); + notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user, ruleChainMetaData); if (RuleChainType.EDGE.equals(ruleChain.getType())) { notificationEntityService.notifySendMsgToEdgeService(tenantId, ruleChain.getId(), EdgeEventActionType.UPDATED); @@ -302,100 +316,95 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement notificationEntityService.notifySendMsgToEdgeService(tenantId, updatedRuleChain.getId(), EdgeEventActionType.UPDATED); } else { RuleChainMetaData updatedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, updatedRuleChain.getId())); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, updatedRuleChain.getId(), - updatedRuleChain, user, ActionType.UPDATED, false, null, updatedRuleChainMetaData); + notificationEntityService.logEntityAction(tenantId, updatedRuleChain.getId(), updatedRuleChain, + ActionType.UPDATED, user, updatedRuleChainMetaData); } } return savedRuleChainMetaData; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - null, user, ActionType.ADDED, false, e, ruleChainMetaData); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.ADDED, + user, e, ruleChainMetaData); + throw e; } } @Override - public RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, SecurityUser user) throws ThingsboardException { + public RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, User user) throws ThingsboardException { RuleChainId ruleChainId = ruleChain.getId(); + EdgeId edgeId = edge.getId(); try { - RuleChain savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(tenantId, ruleChainId, edge.getId())); + RuleChain savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(tenantId, ruleChainId, edgeId)); notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, ruleChainId, - null, edge.getId(), - savedRuleChain, ActionType.ASSIGNED_TO_EDGE, - user, ruleChainId.toString(), edge.getId().toString(), edge.getName()); + null, edgeId, savedRuleChain, ActionType.ASSIGNED_TO_EDGE, + user, ruleChainId.toString(), edgeId.toString(), edge.getName()); return savedRuleChain; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - null, user, ActionType.ASSIGNED_TO_EDGE, false, e, ruleChainId.toString(), edge.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), + ActionType.ASSIGNED_TO_EDGE, user, e, ruleChainId.toString(), edgeId.toString()); + throw e; } } @Override - public RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, SecurityUser user) throws ThingsboardException { + public RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, User user) throws ThingsboardException { RuleChainId ruleChainId = ruleChain.getId(); + EdgeId edgeId = edge.getId(); try { - RuleChain savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(tenantId, ruleChainId, edge.getId(), false)); + RuleChain savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(tenantId, ruleChainId, edgeId, false)); notificationEntityService.notifyAssignOrUnassignEntityToEdge(tenantId, ruleChainId, - null, edge.getId(), - savedRuleChain, ActionType.UNASSIGNED_FROM_EDGE, - user, ruleChainId.toString(), edge.getId().toString(), edge.getName()); + null, edgeId, savedRuleChain, ActionType.UNASSIGNED_FROM_EDGE, + user, ruleChainId.toString(), edgeId.toString(), edge.getName()); return savedRuleChain; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - null, user, ActionType.UNASSIGNED_FROM_EDGE, false, e, ruleChainId.toString(), edge.getId().toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), + ActionType.UNASSIGNED_FROM_EDGE, user, e, ruleChainId, edgeId); + throw e; } } @Override - public RuleChain setEdgeTemplateRootRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException { + public RuleChain setEdgeTemplateRootRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException { RuleChainId ruleChainId = ruleChain.getId(); try { ruleChainService.setEdgeTemplateRootRuleChain(tenantId, ruleChainId); - - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, ruleChainId, - ruleChain, user, ActionType.UPDATED, false, null); + notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); return ruleChain; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - null, user, ActionType.UPDATED, false, e, ruleChainId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + user, e, ruleChainId.toString()); + throw e; } } @Override - public RuleChain setAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException { + public RuleChain setAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException { RuleChainId ruleChainId = ruleChain.getId(); try { ruleChainService.setAutoAssignToEdgeRuleChain(tenantId, ruleChainId); - - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, ruleChainId, - ruleChain, user, ActionType.UPDATED, false, null); + notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); return ruleChain; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - null, user, ActionType.UPDATED, false, e, ruleChainId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + user, e, ruleChainId.toString()); + throw e; } } @Override - public RuleChain unsetAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException { + public RuleChain unsetAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException { RuleChainId ruleChainId = ruleChain.getId(); try { ruleChainService.unsetAutoAssignToEdgeRuleChain(tenantId, ruleChainId); - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, ruleChainId, - ruleChain, user, ActionType.UPDATED, false, null); + notificationEntityService.logEntityAction(tenantId, ruleChainId, ruleChain, ActionType.UPDATED, user); return ruleChain; } catch (Exception e) { - notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, emptyId(EntityType.RULE_CHAIN), - null, user, ActionType.UPDATED, false, e, ruleChainId.toString()); - throw handleException(e); + notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.RULE_CHAIN), ActionType.UPDATED, + user, e, ruleChainId.toString()); + throw e; } } - public Set updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, Map labelsMap) { + private Set updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, Map labelsMap) { Set updatedRuleChains = new HashSet<>(); List usageList = getOutputLabelUsage(tenantId, ruleChainId); for (RuleChainOutputLabelsUsage usage : usageList) { diff --git a/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java index dbdba7d4ef..a8c3fed780 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/TbRuleChainService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.rule; +import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.RuleChainId; @@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage; import org.thingsboard.server.common.data.rule.RuleChainUpdateResult; import org.thingsboard.server.service.entitiy.SimpleTbEntityService; -import org.thingsboard.server.service.security.model.SecurityUser; import java.util.List; import java.util.Set; @@ -38,21 +38,20 @@ public interface TbRuleChainService extends SimpleTbEntityService { List updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result); - RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, SecurityUser user) throws ThingsboardException; + RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, User user) throws Exception; - RuleChain setRootRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException; + RuleChain setRootRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException; RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChain ruleChain, RuleChainMetaData ruleChainMetaData, - boolean updateRelated, SecurityUser user) throws ThingsboardException; + boolean updateRelated, User user) throws Exception; - RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, - SecurityUser user) throws ThingsboardException; - RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, - SecurityUser user) throws ThingsboardException; + RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, User user) throws ThingsboardException; - RuleChain setEdgeTemplateRootRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException; + RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, User user) throws ThingsboardException; - RuleChain setAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException; + RuleChain setEdgeTemplateRootRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException; - RuleChain unsetAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, SecurityUser user) throws ThingsboardException; + RuleChain setAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException; + + RuleChain unsetAutoAssignToEdgeRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException; } diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 6aac90b281..d731ef1536 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -159,6 +159,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer } else { return ((Invocable) engine).invokeFunction(functionName, args); } + } catch (ScriptException e) { + throw new ExecutionException(e); } catch (Exception e) { onScriptExecutionError(scriptId, e, functionName); throw new ExecutionException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 3bee5ca720..86b364afd4 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -57,6 +57,9 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Value("${queue.js.max_requests_timeout}") private long maxRequestsTimeout; + @Value("${queue.js.max_exec_requests_timeout:2000}") + private long maxExecRequestsTimeout; + @Getter @Value("${js.remote.max_errors}") private int maxErrors; @@ -170,7 +173,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setScriptIdMSB(scriptId.getMostSignificantBits()) .setScriptIdLSB(scriptId.getLeastSignificantBits()) .setFunctionName(functionName) - .setTimeout((int) maxRequestsTimeout) + .setTimeout((int) maxExecRequestsTimeout) .setScriptBody(scriptBody); for (Object arg : args) { @@ -197,7 +200,6 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Override public void onFailure(Throwable t) { - onScriptExecutionError(scriptId, t, scriptBody); if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { queueTimeoutMsgs.incrementAndGet(); } @@ -212,8 +214,14 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { return invokeResult.getResult(); } else { final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails()); - onScriptExecutionError(scriptId, e, scriptBody); - log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); + if (JsInvokeProtos.JsInvokeErrorCode.TIMEOUT_ERROR.equals(invokeResult.getErrorCode())) { + onScriptExecutionError(scriptId, e, scriptBody); + queueTimeoutMsgs.incrementAndGet(); + } else if (JsInvokeProtos.JsInvokeErrorCode.COMPILATION_ERROR.equals(invokeResult.getErrorCode())) { + onScriptExecutionError(scriptId, e, scriptBody); + } + queueFailedMsgs.incrementAndGet(); + log.debug("[{}] Failed to invoke function due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); throw e; } }, callbackExecutor); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java index 173e3fa25d..a623fc6862 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java @@ -40,23 +40,23 @@ public class TokenOutdatingService { private final CacheManager cacheManager; private final JwtTokenFactory tokenFactory; private final JwtSettings jwtSettings; - private Cache tokenOutdatageTimeCache; + private Cache usersUpdateTimeCache; @PostConstruct protected void initCache() { - tokenOutdatageTimeCache = cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE); + usersUpdateTimeCache = cacheManager.getCache(CacheConstants.USERS_UPDATE_TIME_CACHE); } @EventListener(classes = UserAuthDataChangedEvent.class) - public void onUserAuthDataChanged(UserAuthDataChangedEvent userAuthDataChangedEvent) { - outdateOldUserTokens(userAuthDataChangedEvent.getUserId()); + public void onUserAuthDataChanged(UserAuthDataChangedEvent event) { + usersUpdateTimeCache.put(toKey(event.getUserId()), event.getTs()); } public boolean isOutdated(JwtToken token, UserId userId) { Claims claims = tokenFactory.parseTokenClaims(token).getBody(); long issueTime = claims.getIssuedAt().getTime(); - return Optional.ofNullable(tokenOutdatageTimeCache.get(toKey(userId), Long.class)) + return Optional.ofNullable(usersUpdateTimeCache.get(toKey(userId), Long.class)) .map(outdatageTime -> { if (System.currentTimeMillis() - outdatageTime <= SECONDS.toMillis(jwtSettings.getRefreshTokenExpTime())) { return MILLISECONDS.toSeconds(issueTime) < MILLISECONDS.toSeconds(outdatageTime); @@ -68,17 +68,13 @@ public class TokenOutdatingService { * as all the tokens issued before the outdatage time * are now expired by themselves * */ - tokenOutdatageTimeCache.evict(toKey(userId)); + usersUpdateTimeCache.evict(toKey(userId)); return false; } }) .orElse(false); } - public void outdateOldUserTokens(UserId userId) { - tokenOutdatageTimeCache.put(toKey(userId), System.currentTimeMillis()); - } - private String toKey(UserId userId) { return userId.getId().toString(); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java index af78d307d3..0972b378eb 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/DefaultTwoFactorAuthService.java @@ -149,7 +149,7 @@ public class DefaultTwoFactorAuthService implements TwoFactorAuthService { ConcurrentMap providersRateLimits = rateLimits.computeIfAbsent(userId, i -> new ConcurrentHashMap<>()); TbRateLimits rateLimit = providersRateLimits.get(providerType); - if (rateLimit == null || !rateLimit.getConfig().equals(rateLimitConfig)) { + if (rateLimit == null || !rateLimit.getConfiguration().equals(rateLimitConfig)) { rateLimit = new TbRateLimits(rateLimitConfig, true); providersRateLimits.put(providerType, rateLimit); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java index 63d3233928..419a60b3b9 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/SmsTwoFaProvider.java @@ -63,7 +63,7 @@ public class SmsTwoFaProvider extends OtpBasedTwoFaProvider { public SessionRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ASSET_CACHE, cacheSpecsMap, connectionFactory, configuration, new RedisSerializer<>() { + super(CacheConstants.SESSIONS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>() { @Override public byte[] serialize(TransportProtos.DeviceSessionsCacheEntry deviceSessionsCacheEntry) throws SerializationException { return deviceSessionsCacheEntry.toByteArray(); } @Override - public TransportProtos.DeviceSessionsCacheEntry deserialize(byte[] bytes) throws SerializationException { + public TransportProtos.DeviceSessionsCacheEntry deserialize(DeviceId key, byte[] bytes) throws SerializationException { try { return TransportProtos.DeviceSessionsCacheEntry.parseFrom(bytes); } catch (InvalidProtocolBufferException e) { diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java new file mode 100644 index 0000000000..0f43159e30 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java @@ -0,0 +1,172 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.apiusage.RateLimitService; +import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; +import org.thingsboard.server.service.sync.ie.exporting.impl.BaseEntityExportService; +import org.thingsboard.server.service.sync.ie.exporting.impl.DefaultEntityExportService; +import org.thingsboard.server.service.sync.ie.importing.EntityImportService; +import org.thingsboard.server.service.sync.ie.importing.impl.MissingEntityException; +import org.thingsboard.server.service.sync.vc.LoadEntityException; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +@Slf4j +public class DefaultEntitiesExportImportService implements EntitiesExportImportService { + + private final Map> exportServices = new HashMap<>(); + private final Map> importServices = new HashMap<>(); + + private final RelationService relationService; + private final RateLimitService rateLimitService; + private final TbNotificationEntityService entityNotificationService; + + protected static final List SUPPORTED_ENTITY_TYPES = List.of( + EntityType.CUSTOMER, EntityType.ASSET, EntityType.RULE_CHAIN, + EntityType.DASHBOARD, EntityType.DEVICE_PROFILE, EntityType.DEVICE, + EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE + ); + + + @Override + public , I extends EntityId> EntityExportData exportEntity(EntitiesExportCtx ctx, I entityId) throws ThingsboardException { + if (!rateLimitService.checkEntityExportLimit(ctx.getTenantId())) { + throw new ThingsboardException("Rate limit for entities export is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); + } + + EntityType entityType = entityId.getEntityType(); + EntityExportService> exportService = getExportService(entityType); + + return exportService.getExportData(ctx, entityId); + } + + @Override + public , I extends EntityId> EntityImportResult importEntity(EntitiesImportCtx ctx, EntityExportData exportData) throws ThingsboardException { + if (!rateLimitService.checkEntityImportLimit(ctx.getTenantId())) { + throw new ThingsboardException("Rate limit for entities import is exceeded", ThingsboardErrorCode.TOO_MANY_REQUESTS); + } + if (exportData.getEntity() == null || exportData.getEntity().getId() == null) { + throw new DataValidationException("Invalid entity data"); + } + + EntityType entityType = exportData.getEntityType(); + EntityImportService> importService = getImportService(entityType); + + EntityImportResult importResult = importService.importEntity(ctx, exportData); + ctx.putInternalId(exportData.getExternalId(), importResult.getSavedEntity().getId()); + + ctx.addReferenceCallback(exportData.getExternalId(), importResult.getSaveReferencesCallback()); + ctx.addEventCallback(importResult.getSendEventsCallback()); + return importResult; + } + + @Override + public void saveReferencesAndRelations(EntitiesImportCtx ctx) throws ThingsboardException { + for (Map.Entry callbackEntry : ctx.getReferenceCallbacks().entrySet()) { + EntityId externalId = callbackEntry.getKey(); + ThrowingRunnable saveReferencesCallback = callbackEntry.getValue(); + try { + saveReferencesCallback.run(); + } catch (MissingEntityException e) { + throw new LoadEntityException(externalId, e); + } + } + + relationService.saveRelations(ctx.getTenantId(), new ArrayList<>(ctx.getRelations())); + + for (EntityRelation relation : ctx.getRelations()) { + entityNotificationService.notifyRelation(ctx.getTenantId(), null, + relation, ctx.getUser(), ActionType.RELATION_ADD_OR_UPDATE, relation); + } + } + + + @Override + public Comparator getEntityTypeComparatorForImport() { + return Comparator.comparing(SUPPORTED_ENTITY_TYPES::indexOf); + } + + + @SuppressWarnings("unchecked") + private , D extends EntityExportData> EntityExportService getExportService(EntityType entityType) { + EntityExportService exportService = exportServices.get(entityType); + if (exportService == null) { + throw new IllegalArgumentException("Export for entity type " + entityType + " is not supported"); + } + return (EntityExportService) exportService; + } + + @SuppressWarnings("unchecked") + private , D extends EntityExportData> EntityImportService getImportService(EntityType entityType) { + EntityImportService importService = importServices.get(entityType); + if (importService == null) { + throw new IllegalArgumentException("Import for entity type " + entityType + " is not supported"); + } + return (EntityImportService) importService; + } + + @Autowired + private void setExportServices(DefaultEntityExportService defaultExportService, + Collection> exportServices) { + exportServices.stream() + .sorted(Comparator.comparing(exportService -> exportService.getSupportedEntityTypes().size(), Comparator.reverseOrder())) + .forEach(exportService -> { + exportService.getSupportedEntityTypes().forEach(entityType -> { + this.exportServices.put(entityType, exportService); + }); + }); + SUPPORTED_ENTITY_TYPES.forEach(entityType -> { + this.exportServices.putIfAbsent(entityType, defaultExportService); + }); + } + + @Autowired + private void setImportServices(Collection> importServices) { + importServices.forEach(entityImportService -> { + this.importServices.put(entityImportService.getEntityType(), entityImportService); + }); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/EntitiesExportImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/EntitiesExportImportService.java new file mode 100644 index 0000000000..8657528dac --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/EntitiesExportImportService.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.service.sync.ie; + +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +import java.util.Comparator; + +public interface EntitiesExportImportService { + + , I extends EntityId> EntityExportData exportEntity(EntitiesExportCtx ctx, I entityId) throws ThingsboardException; + + , I extends EntityId> EntityImportResult importEntity(EntitiesImportCtx ctx, EntityExportData exportData) throws ThingsboardException; + + + void saveReferencesAndRelations(EntitiesImportCtx ctx) throws ThingsboardException; + + Comparator getEntityTypeComparatorForImport(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java new file mode 100644 index 0000000000..6e06144ae2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/DefaultExportableEntitiesService.java @@ -0,0 +1,210 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.AccessControlService; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +@Slf4j +public class DefaultExportableEntitiesService implements ExportableEntitiesService { + + private final Map> daos = new HashMap<>(); + private final Map> removers = new HashMap<>(); + + private final AccessControlService accessControlService; + + + @Override + public , I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId) { + EntityType entityType = externalId.getEntityType(); + Dao dao = getDao(entityType); + + E entity = null; + + if (dao instanceof ExportableEntityDao) { + ExportableEntityDao exportableEntityDao = (ExportableEntityDao) dao; + entity = exportableEntityDao.findByTenantIdAndExternalId(tenantId.getId(), externalId.getId()); + } + if (entity == null || !belongsToTenant(entity, tenantId)) { + return null; + } + + return entity; + } + + @Override + public , I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id) { + E entity = findEntityById(id); + + if (entity == null || !belongsToTenant(entity, tenantId)) { + return null; + } + return entity; + } + + @Override + public , I extends EntityId> E findEntityById(I id) { + EntityType entityType = id.getEntityType(); + Dao dao = getDao(entityType); + if (dao == null) { + throw new IllegalArgumentException("Unsupported entity type " + entityType); + } + + return dao.findById(TenantId.SYS_TENANT_ID, id.getId()); + } + + @Override + public , I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name) { + Dao dao = getDao(entityType); + + E entity = null; + + if (dao instanceof ExportableEntityDao) { + ExportableEntityDao exportableEntityDao = (ExportableEntityDao) dao; + try { + entity = exportableEntityDao.findByTenantIdAndName(tenantId.getId(), name); + } catch (UnsupportedOperationException ignored) { + } + } + if (entity == null || !belongsToTenant(entity, tenantId)) { + return null; + } + + return entity; + } + + @Override + public , I extends EntityId> PageData findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink) { + ExportableEntityDao dao = getExportableEntityDao(entityType); + if (dao != null) { + return dao.findByTenantId(tenantId.getId(), pageLink); + } else { + return new PageData<>(); + } + } + + @Override + public I getExternalIdByInternal(I internalId) { + ExportableEntityDao dao = getExportableEntityDao(internalId.getEntityType()); + if (dao != null) { + return dao.getExternalIdByInternal(internalId); + } else { + return null; + } + } + + private boolean belongsToTenant(HasId entity, TenantId tenantId) { + return tenantId.equals(((HasTenantId) entity).getTenantId()); + } + + + @Override + public void removeById(TenantId tenantId, I id) { + EntityType entityType = id.getEntityType(); + BiConsumer entityRemover = removers.get(entityType); + if (entityRemover == null) { + throw new IllegalArgumentException("Unsupported entity type " + entityType); + } + entityRemover.accept(tenantId, id); + } + + private > ExportableEntityDao getExportableEntityDao(EntityType entityType) { + Dao dao = getDao(entityType); + if (dao instanceof ExportableEntityDao) { + return (ExportableEntityDao) dao; + } else { + return null; + } + } + + @SuppressWarnings("unchecked") + private Dao getDao(EntityType entityType) { + return (Dao) daos.get(entityType); + } + + @Autowired + private void setDaos(Collection> daos) { + daos.forEach(dao -> { + if (dao.getEntityType() != null) { + this.daos.put(dao.getEntityType(), dao); + } + }); + } + + @Autowired + private void setRemovers(CustomerService customerService, AssetService assetService, RuleChainService ruleChainService, + DashboardService dashboardService, DeviceProfileService deviceProfileService, + DeviceService deviceService, WidgetsBundleService widgetsBundleService) { + removers.put(EntityType.CUSTOMER, (tenantId, entityId) -> { + customerService.deleteCustomer(tenantId, (CustomerId) entityId); + }); + removers.put(EntityType.ASSET, (tenantId, entityId) -> { + assetService.deleteAsset(tenantId, (AssetId) entityId); + }); + removers.put(EntityType.RULE_CHAIN, (tenantId, entityId) -> { + ruleChainService.deleteRuleChainById(tenantId, (RuleChainId) entityId); + }); + removers.put(EntityType.DASHBOARD, (tenantId, entityId) -> { + dashboardService.deleteDashboard(tenantId, (DashboardId) entityId); + }); + removers.put(EntityType.DEVICE_PROFILE, (tenantId, entityId) -> { + deviceProfileService.deleteDeviceProfile(tenantId, (DeviceProfileId) entityId); + }); + removers.put(EntityType.DEVICE, (tenantId, entityId) -> { + deviceService.deleteDevice(tenantId, (DeviceId) entityId); + }); + removers.put(EntityType.WIDGETS_BUNDLE, (tenantId, entityId) -> { + widgetsBundleService.deleteWidgetsBundle(tenantId, (WidgetsBundleId) entityId); + }); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/EntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/EntityExportService.java new file mode 100644 index 0000000000..505d1b2a3f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/EntityExportService.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting; + +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +public interface EntityExportService, D extends EntityExportData> { + + D getExportData(EntitiesExportCtx ctx, I entityId) throws ThingsboardException; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java new file mode 100644 index 0000000000..0a99979e8c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/ExportableEntitiesService.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting; + +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; + +public interface ExportableEntitiesService { + + , I extends EntityId> E findEntityByTenantIdAndExternalId(TenantId tenantId, I externalId); + + , I extends EntityId> E findEntityByTenantIdAndId(TenantId tenantId, I id); + + , I extends EntityId> E findEntityById(I id); + + , I extends EntityId> E findEntityByTenantIdAndName(TenantId tenantId, EntityType entityType, String name); + + , I extends EntityId> PageData findEntitiesByTenantId(TenantId tenantId, EntityType entityType, PageLink pageLink); + + I getExternalIdByInternal(I internalId); + + void removeById(TenantId tenantId, I id); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java new file mode 100644 index 0000000000..8875491d42 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/AssetExportService.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting.impl; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +import java.util.Set; + +@Service +@TbCoreComponent +public class AssetExportService extends BaseEntityExportService> { + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, Asset asset, EntityExportData exportData) { + asset.setCustomerId(getExternalIdOrElseInternal(ctx, asset.getCustomerId())); + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.ASSET); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java new file mode 100644 index 0000000000..01626368dc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java @@ -0,0 +1,50 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +import java.util.Set; + +public abstract class BaseEntityExportService, D extends EntityExportData> extends DefaultEntityExportService { + + @Override + protected void setAdditionalExportData(EntitiesExportCtx ctx, E entity, D exportData) throws ThingsboardException { + setRelatedEntities(ctx, entity, (D) exportData); + super.setAdditionalExportData(ctx, entity, exportData); + } + + protected void setRelatedEntities(EntitiesExportCtx ctx, E mainEntity, D exportData) { + } + + protected D newExportData() { + return (D) new EntityExportData(); + } + + public abstract Set getSupportedEntityTypes(); + + protected void replaceUuidsRecursively(EntitiesExportCtx ctx, JsonNode node, Set skipFieldsSet) { + JacksonUtil.replaceUuidsRecursively(node, skipFieldsSet, uuid -> getExternalIdOrElseInternalByUuid(ctx, uuid)); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.java new file mode 100644 index 0000000000..9a3b195b57 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DashboardExportService.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.service.sync.ie.exporting.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Lists; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; +import org.thingsboard.common.util.RegexUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Set; +import java.util.UUID; + +@Service +@TbCoreComponent +public class DashboardExportService extends BaseEntityExportService> { + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, Dashboard dashboard, EntityExportData exportData) { + if (CollectionUtils.isNotEmpty(dashboard.getAssignedCustomers())) { + dashboard.getAssignedCustomers().forEach(customerInfo -> { + customerInfo.setCustomerId(getExternalIdOrElseInternal(ctx, customerInfo.getCustomerId())); + }); + } + for (JsonNode entityAlias : dashboard.getEntityAliasesConfig()) { + replaceUuidsRecursively(ctx, entityAlias, Collections.emptySet()); + } + for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) { + replaceUuidsRecursively(ctx, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.singleton("id")); + } + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.DASHBOARD); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java new file mode 100644 index 0000000000..a00a7c09aa --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DefaultEntityExportService.java @@ -0,0 +1,184 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting.impl; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.context.annotation.Primary; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.sync.ie.AttributeExportData; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.relation.RelationDao; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.ie.exporting.EntityExportService; +import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.stream.Collectors; + +@Service +@TbCoreComponent +@Primary +public class DefaultEntityExportService, D extends EntityExportData> implements EntityExportService { + + @Autowired + @Lazy + protected ExportableEntitiesService exportableEntitiesService; + @Autowired + private RelationDao relationDao; + @Autowired + private AttributesService attributesService; + + @Override + public final D getExportData(EntitiesExportCtx ctx, I entityId) throws ThingsboardException { + D exportData = newExportData(); + + E entity = exportableEntitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entityId); + if (entity == null) { + throw new IllegalArgumentException(entityId.getEntityType() + " [" + entityId.getId() + "] not found"); + } + + exportData.setEntity(entity); + exportData.setEntityType(entityId.getEntityType()); + setAdditionalExportData(ctx, entity, exportData); + + var externalId = entity.getExternalId() != null ? entity.getExternalId() : entity.getId(); + ctx.putExternalId(entityId, externalId); + entity.setId(externalId); + entity.setTenantId(null); + + return exportData; + } + + protected void setAdditionalExportData(EntitiesExportCtx ctx, E entity, D exportData) throws ThingsboardException { + var exportSettings = ctx.getSettings(); + if (exportSettings.isExportRelations()) { + List relations = exportRelations(ctx, entity); + relations.forEach(relation -> { + relation.setFrom(getExternalIdOrElseInternal(ctx, relation.getFrom())); + relation.setTo(getExternalIdOrElseInternal(ctx, relation.getTo())); + }); + exportData.setRelations(relations); + } + if (exportSettings.isExportAttributes()) { + Map> attributes = exportAttributes(ctx, entity); + exportData.setAttributes(attributes); + } + } + + private List exportRelations(EntitiesExportCtx ctx, E entity) throws ThingsboardException { + List relations = new ArrayList<>(); + + List inboundRelations = relationDao.findAllByTo(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON); + relations.addAll(inboundRelations); + + List outboundRelations = relationDao.findAllByFrom(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON); + relations.addAll(outboundRelations); + return relations; + } + + private Map> exportAttributes(EntitiesExportCtx ctx, E entity) throws ThingsboardException { + List scopes; + if (entity.getId().getEntityType() == EntityType.DEVICE) { + scopes = List.of(DataConstants.SERVER_SCOPE, DataConstants.SHARED_SCOPE); + } else { + scopes = Collections.singletonList(DataConstants.SERVER_SCOPE); + } + Map> attributes = new LinkedHashMap<>(); + scopes.forEach(scope -> { + try { + attributes.put(scope, attributesService.findAll(ctx.getTenantId(), entity.getId(), scope).get().stream() + .map(attribute -> { + AttributeExportData attributeExportData = new AttributeExportData(); + attributeExportData.setKey(attribute.getKey()); + attributeExportData.setLastUpdateTs(attribute.getLastUpdateTs()); + attributeExportData.setStrValue(attribute.getStrValue().orElse(null)); + attributeExportData.setDoubleValue(attribute.getDoubleValue().orElse(null)); + attributeExportData.setLongValue(attribute.getLongValue().orElse(null)); + attributeExportData.setBooleanValue(attribute.getBooleanValue().orElse(null)); + attributeExportData.setJsonValue(attribute.getJsonValue().orElse(null)); + return attributeExportData; + }) + .collect(Collectors.toList())); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + return attributes; + } + + protected ID getExternalIdOrElseInternal(EntitiesExportCtx ctx, ID internalId) { + if (internalId == null || internalId.isNullUid()) return internalId; + var result = ctx.getExternalId(internalId); + if (result == null) { + result = Optional.ofNullable(exportableEntitiesService.getExternalIdByInternal(internalId)) + .orElse(internalId); + ctx.putExternalId(internalId, result); + } + return result; + } + + protected UUID getExternalIdOrElseInternalByUuid(EntitiesExportCtx ctx, UUID internalUuid) { + for (EntityType entityType : EntityType.values()) { + EntityId internalId; + try { + internalId = EntityIdFactory.getByTypeAndUuid(entityType, internalUuid); + } catch (Exception e) { + continue; + } + EntityId externalId = ctx.getExternalId(internalId); + if (externalId != null) { + return externalId.getId(); + } + } + for (EntityType entityType : EntityType.values()) { + EntityId internalId; + try { + internalId = EntityIdFactory.getByTypeAndUuid(entityType, internalUuid); + } catch (Exception e) { + continue; + } + EntityId externalId = exportableEntitiesService.getExternalIdByInternal(internalId); + if (externalId != null) { + ctx.putExternalId(internalId, externalId); + return externalId.getId(); + } + } + return internalUuid; + } + + protected D newExportData() { + return (D) new EntityExportData(); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java new file mode 100644 index 0000000000..bc5136bee6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceExportService.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.sync.ie.DeviceExportData; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +import java.util.Set; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class DeviceExportService extends BaseEntityExportService { + + private final DeviceCredentialsService deviceCredentialsService; + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, Device device, DeviceExportData exportData) { + device.setCustomerId(getExternalIdOrElseInternal(ctx, device.getCustomerId())); + device.setDeviceProfileId(getExternalIdOrElseInternal(ctx, device.getDeviceProfileId())); + if (ctx.getSettings().isExportCredentials()) { + var credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(ctx.getTenantId(), device.getId()); + credentials.setId(null); + credentials.setDeviceId(null); + exportData.setCredentials(credentials); + } + } + + @Override + protected DeviceExportData newExportData() { + return new DeviceExportData(); + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.DEVICE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.java new file mode 100644 index 0000000000..46423c66ef --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/DeviceProfileExportService.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.service.sync.ie.exporting.impl; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +import java.util.Set; + +@Service +@TbCoreComponent +public class DeviceProfileExportService extends BaseEntityExportService> { + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, DeviceProfile deviceProfile, EntityExportData exportData) { + deviceProfile.setDefaultDashboardId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultDashboardId())); + deviceProfile.setDefaultRuleChainId(getExternalIdOrElseInternal(ctx, deviceProfile.getDefaultRuleChainId())); + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.DEVICE_PROFILE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/EntityViewExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/EntityViewExportService.java new file mode 100644 index 0000000000..3fff890d3a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/EntityViewExportService.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.service.sync.ie.exporting.impl; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +import java.util.Set; + +@Service +@TbCoreComponent +public class EntityViewExportService extends BaseEntityExportService> { + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, EntityView entityView, EntityExportData exportData) { + entityView.setEntityId(getExternalIdOrElseInternal(ctx, entityView.getEntityId())); + entityView.setCustomerId(getExternalIdOrElseInternal(ctx, entityView.getCustomerId())); + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.ENTITY_VIEW); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java new file mode 100644 index 0000000000..802a03ab94 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/RuleChainExportService.java @@ -0,0 +1,76 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.sync.ie.RuleChainExportData; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; +import org.thingsboard.common.util.RegexUtils; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class RuleChainExportService extends BaseEntityExportService { + + private final RuleChainService ruleChainService; + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, RuleChain ruleChain, RuleChainExportData exportData) { + RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(ctx.getTenantId(), ruleChain.getId()); + Optional.ofNullable(metaData.getNodes()).orElse(Collections.emptyList()) + .forEach(ruleNode -> { + ruleNode.setRuleChainId(null); + ctx.putExternalId(ruleNode.getId(), ruleNode.getExternalId()); + ruleNode.setId(ctx.getExternalId(ruleNode.getId())); + ruleNode.setCreatedTime(0); + ruleNode.setExternalId(null); + replaceUuidsRecursively(ctx, ruleNode.getConfiguration(), Collections.emptySet()); + }); + Optional.ofNullable(metaData.getRuleChainConnections()).orElse(Collections.emptyList()) + .forEach(ruleChainConnectionInfo -> { + ruleChainConnectionInfo.setTargetRuleChainId(getExternalIdOrElseInternal(ctx, ruleChainConnectionInfo.getTargetRuleChainId())); + }); + exportData.setMetaData(metaData); + if (ruleChain.getFirstRuleNodeId() != null) { + ruleChain.setFirstRuleNodeId(ctx.getExternalId(ruleChain.getFirstRuleNodeId())); + } + } + + @Override + protected RuleChainExportData newExportData() { + return new RuleChainExportData(); + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.RULE_CHAIN); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java new file mode 100644 index 0000000000..d3dd23910f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/WidgetsBundleExportService.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.exporting.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; + +import java.util.List; +import java.util.Set; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class WidgetsBundleExportService extends BaseEntityExportService { + + private final WidgetTypeService widgetTypeService; + + @Override + protected void setRelatedEntities(EntitiesExportCtx ctx, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData) { + if (widgetsBundle.getTenantId() == null || widgetsBundle.getTenantId().isNullUid()) { + throw new IllegalArgumentException("Export of system Widget Bundles is not allowed"); + } + + List widgets = widgetTypeService.findWidgetTypesDetailsByTenantIdAndBundleAlias(ctx.getTenantId(), widgetsBundle.getAlias()); + exportData.setWidgets(widgets); + } + + @Override + protected WidgetsBundleExportData newExportData() { + return new WidgetsBundleExportData(); + } + + @Override + public Set getSupportedEntityTypes() { + return Set.of(EntityType.WIDGETS_BUNDLE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/EntityImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/EntityImportService.java new file mode 100644 index 0000000000..9437f21e60 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/EntityImportService.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing; + +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +public interface EntityImportService, D extends EntityExportData> { + + EntityImportResult importEntity(EntitiesImportCtx ctx, D exportData) throws ThingsboardException; + + EntityType getEntityType(); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java rename to application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java index 81a0068a4d..becf92771f 100644 --- a/application/src/main/java/org/thingsboard/server/service/importing/AbstractBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/AbstractBulkImportService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.importing; +package org.thingsboard.server.service.sync.ie.importing.csv; import com.google.common.util.concurrent.FutureCallback; import com.google.gson.JsonObject; @@ -39,12 +39,14 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.controller.BaseController; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.action.EntityActionService; -import org.thingsboard.server.service.importing.BulkImportRequest.ColumnMapping; import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; @@ -67,7 +69,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -165,7 +166,7 @@ public abstract class AbstractBulkImportService data) { + private void saveKvs(SecurityUser user, E entity, Map data) { Arrays.stream(BulkImportColumnType.values()) .filter(BulkImportColumnType::isKv) .map(kvType -> { @@ -249,7 +250,7 @@ public abstract class AbstractBulkImportService columnsMappings = request.getMapping().getColumns(); + List columnsMappings = request.getMapping().getColumns(); return records.stream() .map(record -> { EntityData entityData = new EntityData(); @@ -280,7 +281,7 @@ public abstract class AbstractBulkImportService fields = new LinkedHashMap<>(); - private final Map kvs = new LinkedHashMap<>(); + private final Map kvs = new LinkedHashMap<>(); private int lineNumber; } diff --git a/application/src/main/java/org/thingsboard/server/service/importing/ImportedEntityInfo.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/ImportedEntityInfo.java similarity index 92% rename from application/src/main/java/org/thingsboard/server/service/importing/ImportedEntityInfo.java rename to application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/ImportedEntityInfo.java index 45c2551be2..d48e9a3d23 100644 --- a/application/src/main/java/org/thingsboard/server/service/importing/ImportedEntityInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/ImportedEntityInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.importing; +package org.thingsboard.server.service.sync.ie.importing.csv; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java new file mode 100644 index 0000000000..3225668004 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class AssetImportService extends BaseEntityImportService> { + + private final AssetService assetService; + + @Override + protected void setOwner(TenantId tenantId, Asset asset, IdProvider idProvider) { + asset.setTenantId(tenantId); + asset.setCustomerId(idProvider.getInternalId(asset.getCustomerId())); + } + + @Override + protected Asset prepare(EntitiesImportCtx ctx, Asset asset, Asset old, EntityExportData exportData, IdProvider idProvider) { + return asset; + } + + @Override + protected Asset saveOrUpdate(EntitiesImportCtx ctx, Asset asset, EntityExportData exportData, IdProvider idProvider) { + return assetService.saveAsset(asset); + } + + @Override + protected Asset deepCopy(Asset asset) { + return new Asset(asset); + } + + @Override + protected void cleanupForComparison(Asset e) { + super.cleanupForComparison(e); + if (e.getCustomerId() != null && e.getCustomerId().isNullUid()) { + e.setCustomerId(null); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.ASSET; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java new file mode 100644 index 0000000000..4fdd6cf6ab --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java @@ -0,0 +1,400 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.api.client.util.Objects; +import com.google.common.util.concurrent.FutureCallback; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.sync.ie.AttributeExportData; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.dao.relation.RelationDao; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.service.action.EntityActionService; +import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; +import org.thingsboard.server.service.sync.ie.importing.EntityImportService; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +public abstract class BaseEntityImportService, D extends EntityExportData> implements EntityImportService { + + @Autowired + @Lazy + private ExportableEntitiesService exportableEntitiesService; + @Autowired + private RelationService relationService; + @Autowired + private RelationDao relationDao; + @Autowired + private TelemetrySubscriptionService tsSubService; + @Autowired + protected EntityActionService entityActionService; + @Autowired + protected TbClusterService clusterService; + @Autowired + protected TbNotificationEntityService entityNotificationService; + + @Transactional(rollbackFor = Exception.class) + @Override + public EntityImportResult importEntity(EntitiesImportCtx ctx, D exportData) throws ThingsboardException { + EntityImportResult importResult = new EntityImportResult<>(); + ctx.setCurrentImportResult(importResult); + importResult.setEntityType(getEntityType()); + IdProvider idProvider = new IdProvider(ctx, importResult); + + E entity = exportData.getEntity(); + entity.setExternalId(entity.getId()); + + E existingEntity = findExistingEntity(ctx, entity, idProvider); + importResult.setOldEntity(existingEntity); + + setOwner(ctx.getTenantId(), entity, idProvider); + if (existingEntity == null) { + entity.setId(null); + } else { + entity.setId(existingEntity.getId()); + entity.setCreatedTime(existingEntity.getCreatedTime()); + } + + E prepared = prepare(ctx, entity, existingEntity, exportData, idProvider); + + boolean saveOrUpdate = existingEntity == null || compare(ctx, exportData, prepared, existingEntity); + + if (saveOrUpdate) { + E savedEntity = saveOrUpdate(ctx, prepared, exportData, idProvider); + boolean created = existingEntity == null; + importResult.setCreated(created); + importResult.setUpdated(!created); + importResult.setSavedEntity(savedEntity); + ctx.putInternalId(exportData.getExternalId(), savedEntity.getId()); + } else { + importResult.setSavedEntity(existingEntity); + ctx.putInternalId(exportData.getExternalId(), existingEntity.getId()); + importResult.setUpdatedRelatedEntities(updateRelatedEntitiesIfUnmodified(ctx, prepared, exportData, idProvider)); + } + + processAfterSaved(ctx, importResult, exportData, idProvider); + + return importResult; + } + + protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, E prepared, D exportData, IdProvider idProvider) { + return false; + } + + @Override + public abstract EntityType getEntityType(); + + protected abstract void setOwner(TenantId tenantId, E entity, IdProvider idProvider); + + protected abstract E prepare(EntitiesImportCtx ctx, E entity, E oldEntity, D exportData, IdProvider idProvider); + + protected boolean compare(EntitiesImportCtx ctx, D exportData, E prepared, E existing) { + var newCopy = deepCopy(prepared); + var existingCopy = deepCopy(existing); + cleanupForComparison(newCopy); + cleanupForComparison(existingCopy); + var result = !newCopy.equals(existingCopy); + if (result) { + log.debug("[{}] Found update.", prepared.getId()); + log.debug("[{}] From: {}", prepared.getId(), newCopy); + log.debug("[{}] To: {}", prepared.getId(), existingCopy); + } + return result; + } + + protected abstract E deepCopy(E e); + + protected void cleanupForComparison(E e) { + e.setTenantId(null); + e.setCreatedTime(0); + } + + protected abstract E saveOrUpdate(EntitiesImportCtx ctx, E entity, D exportData, IdProvider idProvider); + + + protected void processAfterSaved(EntitiesImportCtx ctx, EntityImportResult importResult, D exportData, IdProvider idProvider) throws ThingsboardException { + E savedEntity = importResult.getSavedEntity(); + E oldEntity = importResult.getOldEntity(); + + if (importResult.isCreated() || importResult.isUpdated()) { + importResult.addSendEventsCallback(() -> onEntitySaved(ctx.getUser(), savedEntity, oldEntity)); + } + + if (ctx.isUpdateRelations() && exportData.getRelations() != null) { + importRelations(ctx, exportData.getRelations(), importResult, idProvider); + } + if (ctx.isSaveAttributes() && exportData.getAttributes() != null) { + if (exportData.getAttributes().values().stream().anyMatch(d -> !d.isEmpty())) { + importResult.setUpdatedRelatedEntities(true); + } + importAttributes(ctx.getUser(), exportData.getAttributes(), importResult); + } + } + + private void importRelations(EntitiesImportCtx ctx, List relations, EntityImportResult importResult, IdProvider idProvider) { + var tenantId = ctx.getTenantId(); + E entity = importResult.getSavedEntity(); + importResult.addSaveReferencesCallback(() -> { + for (EntityRelation relation : relations) { + if (!relation.getTo().equals(entity.getId())) { + relation.setTo(idProvider.getInternalId(relation.getTo())); + } + if (!relation.getFrom().equals(entity.getId())) { + relation.setFrom(idProvider.getInternalId(relation.getFrom())); + } + } + + Map relationsMap = new LinkedHashMap<>(); + relations.forEach(r -> relationsMap.put(r, r)); + + if (importResult.getOldEntity() != null) { + List existingRelations = new ArrayList<>(); + existingRelations.addAll(relationDao.findAllByTo(tenantId, entity.getId(), RelationTypeGroup.COMMON)); + existingRelations.addAll(relationDao.findAllByFrom(tenantId, entity.getId(), RelationTypeGroup.COMMON)); + // dao is used here instead of service to avoid getting cached values, because relationService.deleteRelation will evict value from cache only after transaction is committed + + for (EntityRelation existingRelation : existingRelations) { + EntityRelation relation = relationsMap.get(existingRelation); + if (relation == null) { + importResult.setUpdatedRelatedEntities(true); + relationService.deleteRelation(ctx.getTenantId(), existingRelation.getFrom(), existingRelation.getTo(), existingRelation.getType(), existingRelation.getTypeGroup()); + importResult.addSendEventsCallback(() -> { + entityNotificationService.notifyRelation(tenantId, null, + existingRelation, ctx.getUser(), ActionType.RELATION_DELETED, existingRelation); + }); + } else if (Objects.equal(relation.getAdditionalInfo(), existingRelation.getAdditionalInfo())) { + relationsMap.remove(relation); + } + } + } + if (!relationsMap.isEmpty()) { + importResult.setUpdatedRelatedEntities(true); + ctx.addRelations(relationsMap.values()); + } + }); + } + + private void importAttributes(User user, Map> attributes, EntityImportResult importResult) { + E entity = importResult.getSavedEntity(); + importResult.addSaveReferencesCallback(() -> { + attributes.forEach((scope, attributesExportData) -> { + List attributeKvEntries = attributesExportData.stream() + .map(attributeExportData -> { + KvEntry kvEntry; + String key = attributeExportData.getKey(); + if (attributeExportData.getStrValue() != null) { + kvEntry = new StringDataEntry(key, attributeExportData.getStrValue()); + } else if (attributeExportData.getBooleanValue() != null) { + kvEntry = new BooleanDataEntry(key, attributeExportData.getBooleanValue()); + } else if (attributeExportData.getDoubleValue() != null) { + kvEntry = new DoubleDataEntry(key, attributeExportData.getDoubleValue()); + } else if (attributeExportData.getLongValue() != null) { + kvEntry = new LongDataEntry(key, attributeExportData.getLongValue()); + } else if (attributeExportData.getJsonValue() != null) { + kvEntry = new JsonDataEntry(key, attributeExportData.getJsonValue()); + } else { + throw new IllegalArgumentException("Invalid attribute export data"); + } + return new BaseAttributeKvEntry(kvEntry, attributeExportData.getLastUpdateTs()); + }) + .collect(Collectors.toList()); + // fixme: attributes are saved outside the transaction + tsSubService.saveAndNotify(user.getTenantId(), entity.getId(), scope, attributeKvEntries, new FutureCallback() { + @Override + public void onSuccess(@Nullable Void unused) { + } + + @Override + public void onFailure(Throwable thr) { + log.error("Failed to import attributes for {} {}", entity.getId().getEntityType(), entity.getId(), thr); + } + }); + }); + }); + } + + protected void onEntitySaved(User user, E savedEntity, E oldEntity) throws ThingsboardException { + entityNotificationService.notifyCreateOrUpdateEntity(user.getTenantId(), savedEntity.getId(), savedEntity, + null, oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, user); + } + + + @SuppressWarnings("unchecked") + protected E findExistingEntity(EntitiesImportCtx ctx, E entity, IdProvider idProvider) { + return (E) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(ctx.getTenantId(), entity.getId())) + .or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(ctx.getTenantId(), entity.getId()))) + .or(() -> { + if (ctx.isFindExistingByName()) { + return Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndName(ctx.getTenantId(), getEntityType(), entity.getName())); + } else { + return Optional.empty(); + } + }) + .orElse(null); + } + + @SuppressWarnings("unchecked") + private HasId findInternalEntity(TenantId tenantId, ID externalId) { + return (HasId) Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndExternalId(tenantId, externalId)) + .or(() -> Optional.ofNullable(exportableEntitiesService.findEntityByTenantIdAndId(tenantId, externalId))) + .orElseThrow(() -> new MissingEntityException(externalId)); + } + + + @SuppressWarnings("unchecked") + @RequiredArgsConstructor + protected class IdProvider { + private final EntitiesImportCtx ctx; + private final EntityImportResult importResult; + + public ID getInternalId(ID externalId) { + return getInternalId(externalId, true); + } + + public ID getInternalId(ID externalId, boolean throwExceptionIfNotFound) { + if (externalId == null || externalId.isNullUid()) return null; + + if (EntityType.TENANT.equals(externalId.getEntityType())) { + return (ID) ctx.getTenantId(); + } + + EntityId localId = ctx.getInternalId(externalId); + if (localId != null) { + return (ID) localId; + } + + HasId entity; + try { + entity = findInternalEntity(ctx.getTenantId(), externalId); + } catch (Exception e) { + if (throwExceptionIfNotFound) { + throw e; + } else { + importResult.setUpdatedAllExternalIds(false); + return null; + } + } + ctx.putInternalId(externalId, entity.getId()); + return entity.getId(); + } + + public Optional getInternalIdByUuid(UUID externalUuid, boolean fetchAllUUIDs, Set hints) { + if (externalUuid.equals(EntityId.NULL_UUID)) return Optional.empty(); + + for (EntityType entityType : EntityType.values()) { + Optional externalIdOpt = buildEntityId(entityType, externalUuid); + if (!externalIdOpt.isPresent()) { + continue; + } + EntityId internalId = ctx.getInternalId(externalIdOpt.get()); + if (internalId != null) { + return Optional.of(internalId); + } + } + + if (fetchAllUUIDs) { + for (EntityType entityType : hints) { + Optional internalId = lookupInDb(externalUuid, entityType); + if (internalId.isPresent()) return internalId; + } + for (EntityType entityType : EntityType.values()) { + if (hints.contains(entityType)) { + continue; + } + Optional internalId = lookupInDb(externalUuid, entityType); + if (internalId.isPresent()) return internalId; + } + } + + importResult.setUpdatedAllExternalIds(false); + return Optional.empty(); + } + + private Optional lookupInDb(UUID externalUuid, EntityType entityType) { + Optional externalIdOpt = buildEntityId(entityType, externalUuid); + if (externalIdOpt.isEmpty() || ctx.isNotFound(externalIdOpt.get())) { + return Optional.empty(); + } + EntityId internalId = getInternalId(externalIdOpt.get(), false); + if (internalId != null) { + return Optional.of(internalId); + } else { + ctx.registerNotFound(externalIdOpt.get()); + } + return Optional.empty(); + } + + private Optional buildEntityId(EntityType entityType, UUID externalUuid) { + try { + return Optional.of(EntityIdFactory.getByTypeAndUuid(entityType, externalUuid)); + } catch (Exception e) { + return Optional.empty(); + } + } + + } + + protected T getOldEntityField(O oldEntity, Function getter) { + return oldEntity == null ? null : getter.apply(oldEntity); + } + + protected void replaceIdsRecursively(EntitiesImportCtx ctx, IdProvider idProvider, JsonNode entityAlias, Set skipFieldsSet, LinkedHashSet hints) { + JacksonUtil.replaceUuidsRecursively(entityAlias, skipFieldsSet, + uuid -> idProvider.getInternalIdByUuid(uuid, ctx.isFinalImportAttempt(), hints).map(EntityId::getId).orElse(uuid)); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java new file mode 100644 index 0000000000..d12ed6ecff --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.dao.customer.CustomerDao; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class CustomerImportService extends BaseEntityImportService> { + + private final CustomerService customerService; + private final CustomerDao customerDao; + + @Override + protected void setOwner(TenantId tenantId, Customer customer, IdProvider idProvider) { + customer.setTenantId(tenantId); + } + + @Override + protected Customer prepare(EntitiesImportCtx ctx, Customer customer, Customer old, EntityExportData exportData, IdProvider idProvider) { + if (customer.isPublic()) { + Customer publicCustomer = customerService.findOrCreatePublicCustomer(ctx.getTenantId()); + publicCustomer.setExternalId(customer.getExternalId()); + return publicCustomer; + } else { + return customer; + } + } + + @Override + protected Customer saveOrUpdate(EntitiesImportCtx ctx, Customer customer, EntityExportData exportData, IdProvider idProvider) { + if (!customer.isPublic()) { + return customerService.saveCustomer(customer); + } else { + return customerDao.save(ctx.getTenantId(), customer); + } + } + + @Override + protected Customer deepCopy(Customer customer) { + return new Customer(customer); + } + + @Override + public EntityType getEntityType() { + return EntityType.CUSTOMER; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java new file mode 100644 index 0000000000..5aa97a6f4d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java @@ -0,0 +1,130 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class DashboardImportService extends BaseEntityImportService> { + + private static final LinkedHashSet HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.DASHBOARD, EntityType.DEVICE, EntityType.ASSET)); + + private final DashboardService dashboardService; + + + @Override + protected void setOwner(TenantId tenantId, Dashboard dashboard, IdProvider idProvider) { + dashboard.setTenantId(tenantId); + } + + @Override + protected Dashboard findExistingEntity(EntitiesImportCtx ctx, Dashboard dashboard, IdProvider idProvider) { + Dashboard existingDashboard = super.findExistingEntity(ctx, dashboard, idProvider); + if (existingDashboard == null && ctx.isFindExistingByName()) { + existingDashboard = dashboardService.findTenantDashboardsByTitle(ctx.getTenantId(), dashboard.getName()).stream().findFirst().orElse(null); + } + return existingDashboard; + } + + @Override + protected Dashboard prepare(EntitiesImportCtx ctx, Dashboard dashboard, Dashboard old, EntityExportData exportData, IdProvider idProvider) { + for (JsonNode entityAlias : dashboard.getEntityAliasesConfig()) { + replaceIdsRecursively(ctx, idProvider, entityAlias, Collections.emptySet(), HINTS); + } + for (JsonNode widgetConfig : dashboard.getWidgetsConfig()) { + replaceIdsRecursively(ctx, idProvider, JacksonUtil.getSafely(widgetConfig, "config", "actions"), Collections.singleton("id"), HINTS); + } + return dashboard; + } + + @Override + protected Dashboard saveOrUpdate(EntitiesImportCtx ctx, Dashboard dashboard, EntityExportData exportData, IdProvider idProvider) { + var tenantId = ctx.getTenantId(); + + Set assignedCustomers = Optional.ofNullable(dashboard.getAssignedCustomers()).orElse(Collections.emptySet()).stream() + .peek(customerInfo -> customerInfo.setCustomerId(idProvider.getInternalId(customerInfo.getCustomerId()))) + .collect(Collectors.toSet()); + + if (dashboard.getId() == null) { + dashboard.setAssignedCustomers(assignedCustomers); + dashboard = dashboardService.saveDashboard(dashboard); + for (ShortCustomerInfo customerInfo : assignedCustomers) { + dashboard = dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerInfo.getCustomerId()); + } + } else { + Set existingAssignedCustomers = Optional.ofNullable(dashboardService.findDashboardById(tenantId, dashboard.getId()).getAssignedCustomers()) + .orElse(Collections.emptySet()).stream().map(ShortCustomerInfo::getCustomerId).collect(Collectors.toSet()); + Set newAssignedCustomers = assignedCustomers.stream().map(ShortCustomerInfo::getCustomerId).collect(Collectors.toSet()); + + Set toUnassign = new HashSet<>(existingAssignedCustomers); + toUnassign.removeAll(newAssignedCustomers); + for (CustomerId customerId : toUnassign) { + assignedCustomers = dashboardService.unassignDashboardFromCustomer(tenantId, dashboard.getId(), customerId).getAssignedCustomers(); + } + + Set toAssign = new HashSet<>(newAssignedCustomers); + toAssign.removeAll(existingAssignedCustomers); + for (CustomerId customerId : toAssign) { + assignedCustomers = dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId).getAssignedCustomers(); + } + dashboard.setAssignedCustomers(assignedCustomers); + dashboard = dashboardService.saveDashboard(dashboard); + } + return dashboard; + } + + @Override + protected Dashboard deepCopy(Dashboard dashboard) { + return new Dashboard(dashboard); + } + + @Override + protected boolean compare(EntitiesImportCtx ctx, EntityExportData exportData, Dashboard prepared, Dashboard existing) { + return super.compare(ctx, exportData, prepared, existing) || !prepared.getConfiguration().equals(existing.getConfiguration()); + } + + @Override + public EntityType getEntityType() { + return EntityType.DASHBOARD; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java new file mode 100644 index 0000000000..2cf2c57c11 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceImportService.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.DeviceExportData; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class DeviceImportService extends BaseEntityImportService { + + private final DeviceService deviceService; + private final DeviceCredentialsService credentialsService; + + @Override + protected void setOwner(TenantId tenantId, Device device, IdProvider idProvider) { + device.setTenantId(tenantId); + device.setCustomerId(idProvider.getInternalId(device.getCustomerId())); + } + + @Override + protected Device prepare(EntitiesImportCtx ctx, Device device, Device old, DeviceExportData exportData, IdProvider idProvider) { + device.setDeviceProfileId(idProvider.getInternalId(device.getDeviceProfileId())); + device.setFirmwareId(getOldEntityField(old, Device::getFirmwareId)); + device.setSoftwareId(getOldEntityField(old, Device::getSoftwareId)); + return device; + } + + @Override + protected Device deepCopy(Device d) { + return new Device(d); + } + + @Override + protected void cleanupForComparison(Device e) { + super.cleanupForComparison(e); + if (e.getCustomerId() != null && e.getCustomerId().isNullUid()) { + e.setCustomerId(null); + } + } + + @Override + protected Device saveOrUpdate(EntitiesImportCtx ctx, Device device, DeviceExportData exportData, IdProvider idProvider) { + if (exportData.getCredentials() != null && ctx.isSaveCredentials()) { + exportData.getCredentials().setId(null); + exportData.getCredentials().setDeviceId(null); + return deviceService.saveDeviceWithCredentials(device, exportData.getCredentials()); + } else { + return deviceService.saveDevice(device); + } + } + + @Override + protected boolean updateRelatedEntitiesIfUnmodified(EntitiesImportCtx ctx, Device prepared, DeviceExportData exportData, IdProvider idProvider) { + boolean updated = super.updateRelatedEntitiesIfUnmodified(ctx, prepared, exportData, idProvider); + var credentials = exportData.getCredentials(); + if (credentials != null && ctx.isSaveCredentials()) { + var existing = credentialsService.findDeviceCredentialsByDeviceId(ctx.getTenantId(), prepared.getId()); + credentials.setId(existing.getId()); + credentials.setDeviceId(prepared.getId()); + if (!existing.equals(credentials)) { + credentialsService.updateDeviceCredentials(ctx.getTenantId(), credentials); + updated = true; + } + } + return updated; + } + + @Override + protected void onEntitySaved(User user, Device savedDevice, Device oldDevice) throws ThingsboardException { + entityNotificationService.notifyCreateOrUpdateDevice(user.getTenantId(), savedDevice.getId(), savedDevice.getCustomerId(), + savedDevice, oldDevice, oldDevice == null ? ActionType.ADDED : ActionType.UPDATED, user); + } + + @Override + public EntityType getEntityType() { + return EntityType.DEVICE; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java new file mode 100644 index 0000000000..72be8e6bf7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DeviceProfileImportService.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.ota.OtaPackageStateService; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +import java.util.Objects; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class DeviceProfileImportService extends BaseEntityImportService> { + + private final DeviceProfileService deviceProfileService; + private final OtaPackageStateService otaPackageStateService; + + @Override + protected void setOwner(TenantId tenantId, DeviceProfile deviceProfile, IdProvider idProvider) { + deviceProfile.setTenantId(tenantId); + } + + @Override + protected DeviceProfile prepare(EntitiesImportCtx ctx, DeviceProfile deviceProfile, DeviceProfile old, EntityExportData exportData, IdProvider idProvider) { + deviceProfile.setDefaultRuleChainId(idProvider.getInternalId(deviceProfile.getDefaultRuleChainId())); + deviceProfile.setDefaultDashboardId(idProvider.getInternalId(deviceProfile.getDefaultDashboardId())); + deviceProfile.setFirmwareId(getOldEntityField(old, DeviceProfile::getFirmwareId)); + deviceProfile.setSoftwareId(getOldEntityField(old, DeviceProfile::getSoftwareId)); + return deviceProfile; + } + + @Override + protected DeviceProfile saveOrUpdate(EntitiesImportCtx ctx, DeviceProfile deviceProfile, EntityExportData exportData, IdProvider idProvider) { + return deviceProfileService.saveDeviceProfile(deviceProfile); + } + + @Override + protected void onEntitySaved(User user, DeviceProfile savedDeviceProfile, DeviceProfile oldDeviceProfile) throws ThingsboardException { + clusterService.onDeviceProfileChange(savedDeviceProfile, null); + clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedDeviceProfile.getId(), + oldDeviceProfile == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + otaPackageStateService.update(savedDeviceProfile, + oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getFirmwareId(), savedDeviceProfile.getFirmwareId()), + oldDeviceProfile != null && !Objects.equals(oldDeviceProfile.getSoftwareId(), savedDeviceProfile.getSoftwareId())); + entityNotificationService.notifyCreateOrUpdateOrDelete(savedDeviceProfile.getTenantId(), null, + savedDeviceProfile.getId(), savedDeviceProfile, user, oldDeviceProfile == null ? ActionType.ADDED : ActionType.UPDATED, true, null); + } + + @Override + protected DeviceProfile deepCopy(DeviceProfile deviceProfile) { + return new DeviceProfile(deviceProfile); + } + + @Override + protected void cleanupForComparison(DeviceProfile deviceProfile) { + super.cleanupForComparison(deviceProfile); + deviceProfile.setFirmwareId(null); + deviceProfile.setSoftwareId(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.DEVICE_PROFILE; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.java new file mode 100644 index 0000000000..6856323ecc --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/EntityViewImportService.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.server.service.sync.ie.importing.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class EntityViewImportService extends BaseEntityImportService> { + + private final EntityViewService entityViewService; + + @Lazy + @Autowired + private TbEntityViewService tbEntityViewService; + + @Override + protected void setOwner(TenantId tenantId, EntityView entityView, IdProvider idProvider) { + entityView.setTenantId(tenantId); + entityView.setCustomerId(idProvider.getInternalId(entityView.getCustomerId())); + } + + @Override + protected EntityView prepare(EntitiesImportCtx ctx, EntityView entityView, EntityView old, EntityExportData exportData, IdProvider idProvider) { + entityView.setEntityId(idProvider.getInternalId(entityView.getEntityId())); + return entityView; + } + + @Override + protected EntityView saveOrUpdate(EntitiesImportCtx ctx, EntityView entityView, EntityExportData exportData, IdProvider idProvider) { + return entityViewService.saveEntityView(entityView); + } + + @Override + protected void onEntitySaved(User user, EntityView savedEntityView, EntityView oldEntityView) throws ThingsboardException { + tbEntityViewService.updateEntityViewAttributes(user.getTenantId(), savedEntityView, oldEntityView, user); + super.onEntitySaved(user, savedEntityView, oldEntityView); + clusterService.broadcastEntityStateChangeEvent(savedEntityView.getTenantId(), savedEntityView.getId(), + oldEntityView == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + } + + @Override + protected EntityView deepCopy(EntityView entityView) { + return new EntityView(entityView); + } + + @Override + protected void cleanupForComparison(EntityView e) { + super.cleanupForComparison(e); + if (e.getCustomerId() != null && e.getCustomerId().isNullUid()) { + e.setCustomerId(null); + } + } + + @Override + public EntityType getEntityType() { + return EntityType.ENTITY_VIEW; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java new file mode 100644 index 0000000000..1e869429d8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/ImportServiceException.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +public class ImportServiceException extends RuntimeException{ + private static final long serialVersionUID = -4932715239522125041L; +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.java new file mode 100644 index 0000000000..a0a961bfef --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/MissingEntityException.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.service.sync.ie.importing.impl; + +import lombok.Getter; +import org.thingsboard.server.common.data.id.EntityId; + +public class MissingEntityException extends ImportServiceException { + + private static final long serialVersionUID = 3669135386955906022L; + @Getter + private final EntityId entityId; + + public MissingEntityException(EntityId entityId) { + this.entityId = entityId; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java new file mode 100644 index 0000000000..f01eb15337 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/RuleChainImportService.java @@ -0,0 +1,150 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.sync.ie.RuleChainExportData; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.rule.RuleNodeDao; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Slf4j +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class RuleChainImportService extends BaseEntityImportService { + + private static final LinkedHashSet HINTS = new LinkedHashSet<>(Arrays.asList(EntityType.RULE_CHAIN, EntityType.DEVICE, EntityType.ASSET)); + + private final RuleChainService ruleChainService; + private final RuleNodeDao ruleNodeDao; + + @Override + protected void setOwner(TenantId tenantId, RuleChain ruleChain, IdProvider idProvider) { + ruleChain.setTenantId(tenantId); + } + + @Override + protected RuleChain findExistingEntity(EntitiesImportCtx ctx, RuleChain ruleChain, IdProvider idProvider) { + RuleChain existingRuleChain = super.findExistingEntity(ctx, ruleChain, idProvider); + if (existingRuleChain == null && ctx.isFindExistingByName()) { + existingRuleChain = ruleChainService.findTenantRuleChainsByTypeAndName(ctx.getTenantId(), ruleChain.getType(), ruleChain.getName()).stream().findFirst().orElse(null); + } + return existingRuleChain; + } + + @Override + protected RuleChain prepare(EntitiesImportCtx ctx, RuleChain ruleChain, RuleChain old, RuleChainExportData exportData, IdProvider idProvider) { + RuleChainMetaData metaData = exportData.getMetaData(); + List ruleNodes = Optional.ofNullable(metaData.getNodes()).orElse(Collections.emptyList()); + if (old != null) { + List nodeIds = ruleNodes.stream().map(RuleNode::getId).collect(Collectors.toList()); + List existing = ruleNodeDao.findByExternalIds(old.getId(), nodeIds); + existing.forEach(node -> ctx.putInternalId(node.getExternalId(), node.getId())); + ruleNodes.forEach(node -> { + node.setRuleChainId(old.getId()); + node.setExternalId(node.getId()); + node.setId((RuleNodeId) ctx.getInternalId(node.getId())); + }); + } else { + ruleNodes.forEach(node -> { + node.setRuleChainId(null); + node.setExternalId(node.getId()); + node.setId(null); + }); + } + + ruleNodes.forEach(ruleNode -> replaceIdsRecursively(ctx, idProvider, ruleNode.getConfiguration(), Collections.emptySet(), HINTS)); + Optional.ofNullable(metaData.getRuleChainConnections()).orElse(Collections.emptyList()) + .forEach(ruleChainConnectionInfo -> { + ruleChainConnectionInfo.setTargetRuleChainId(idProvider.getInternalId(ruleChainConnectionInfo.getTargetRuleChainId(), false)); + }); + if (ruleChain.getFirstRuleNodeId() != null) { + ruleChain.setFirstRuleNodeId((RuleNodeId) ctx.getInternalId(ruleChain.getFirstRuleNodeId())); + } + return ruleChain; + } + + @Override + protected RuleChain saveOrUpdate(EntitiesImportCtx ctx, RuleChain ruleChain, RuleChainExportData exportData, IdProvider idProvider) { + ruleChain = ruleChainService.saveRuleChain(ruleChain); + if (ctx.isFinalImportAttempt() || ctx.getCurrentImportResult().isUpdatedAllExternalIds()) { + exportData.getMetaData().setRuleChainId(ruleChain.getId()); + ruleChainService.saveRuleChainMetaData(ctx.getTenantId(), exportData.getMetaData()); + return ruleChainService.findRuleChainById(ctx.getTenantId(), ruleChain.getId()); + } else { + return ruleChain; + } + } + + @Override + protected boolean compare(EntitiesImportCtx ctx, RuleChainExportData exportData, RuleChain prepared, RuleChain existing) { + boolean different = super.compare(ctx, exportData, prepared, existing); + if (!different) { + RuleChainMetaData newMD = exportData.getMetaData(); + RuleChainMetaData existingMD = ruleChainService.loadRuleChainMetaData(ctx.getTenantId(), prepared.getId()); + existingMD.setRuleChainId(null); + different = !newMD.equals(existingMD); + } + return different; + } + + @Override + protected void onEntitySaved(User user, RuleChain savedRuleChain, RuleChain oldRuleChain) throws ThingsboardException { + entityActionService.logEntityAction(user, savedRuleChain.getId(), savedRuleChain, null, + oldRuleChain == null ? ActionType.ADDED : ActionType.UPDATED, null); + if (savedRuleChain.getType() == RuleChainType.CORE) { + clusterService.broadcastEntityStateChangeEvent(user.getTenantId(), savedRuleChain.getId(), + oldRuleChain == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + } else if (savedRuleChain.getType() == RuleChainType.EDGE && oldRuleChain != null) { + entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedRuleChain.getId(), EdgeEventActionType.UPDATED); + } + } + + @Override + protected RuleChain deepCopy(RuleChain ruleChain) { + return new RuleChain(ruleChain); + } + + @Override + public EntityType getEntityType() { + return EntityType.RULE_CHAIN; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java new file mode 100644 index 0000000000..adbbcf6a09 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java @@ -0,0 +1,110 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie.importing.impl; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.sync.ie.WidgetsBundleExportData; +import org.thingsboard.server.common.data.widget.BaseWidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; + +import java.util.Map; +import java.util.stream.Collectors; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +public class WidgetsBundleImportService extends BaseEntityImportService { + + private final WidgetsBundleService widgetsBundleService; + private final WidgetTypeService widgetTypeService; + + @Override + protected void setOwner(TenantId tenantId, WidgetsBundle widgetsBundle, IdProvider idProvider) { + widgetsBundle.setTenantId(tenantId); + } + + @Override + protected WidgetsBundle prepare(EntitiesImportCtx ctx, WidgetsBundle widgetsBundle, WidgetsBundle old, WidgetsBundleExportData exportData, IdProvider idProvider) { + return widgetsBundle; + } + + @Override + protected WidgetsBundle saveOrUpdate(EntitiesImportCtx ctx, WidgetsBundle widgetsBundle, WidgetsBundleExportData exportData, IdProvider idProvider) { + WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); + if (widgetsBundle.getId() == null) { + for (WidgetTypeDetails widget : exportData.getWidgets()) { + widget.setId(null); + widget.setTenantId(ctx.getTenantId()); + widget.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetTypeService.saveWidgetType(widget); + } + } else { + Map existingWidgets = widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(ctx.getTenantId(), savedWidgetsBundle.getAlias()).stream() + .collect(Collectors.toMap(BaseWidgetType::getAlias, w -> w)); + for (WidgetTypeDetails widget : exportData.getWidgets()) { + WidgetTypeInfo existingWidget; + if ((existingWidget = existingWidgets.remove(widget.getAlias())) != null) { + widget.setId(existingWidget.getId()); + widget.setCreatedTime(existingWidget.getCreatedTime()); + } else { + widget.setId(null); + } + widget.setTenantId(ctx.getTenantId()); + widget.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetTypeService.saveWidgetType(widget); + } + existingWidgets.values().stream() + .map(BaseWidgetType::getId) + .forEach(widgetTypeId -> widgetTypeService.deleteWidgetType(ctx.getTenantId(), widgetTypeId)); + } + return savedWidgetsBundle; + } + + @Override + protected boolean compare(EntitiesImportCtx ctx, WidgetsBundleExportData exportData, WidgetsBundle prepared, WidgetsBundle existing) { + return true; + } + + @Override + protected void onEntitySaved(User user, WidgetsBundle savedWidgetsBundle, WidgetsBundle oldWidgetsBundle) throws ThingsboardException { + entityNotificationService.notifySendMsgToEdgeService(user.getTenantId(), savedWidgetsBundle.getId(), + oldWidgetsBundle == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); + } + + @Override + protected WidgetsBundle deepCopy(WidgetsBundle widgetsBundle) { + return new WidgetsBundle(widgetsBundle); + } + + @Override + public EntityType getEntityType() { + return EntityType.WIDGETS_BUNDLE; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java new file mode 100644 index 0000000000..b0e7b67f25 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -0,0 +1,631 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.support.TransactionTemplate; +import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.TbStopWatch; +import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.EntityDataDiff; +import org.thingsboard.server.common.data.sync.vc.EntityDataInfo; +import org.thingsboard.server.common.data.sync.vc.EntityLoadError; +import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.common.data.sync.vc.request.create.AutoVersionCreateConfig; +import org.thingsboard.server.common.data.sync.vc.request.create.ComplexVersionCreateRequest; +import org.thingsboard.server.common.data.sync.vc.request.create.EntityTypeVersionCreateConfig; +import org.thingsboard.server.common.data.sync.vc.request.create.SingleEntityVersionCreateRequest; +import org.thingsboard.server.common.data.sync.vc.request.create.SyncStrategy; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; +import org.thingsboard.server.common.data.sync.vc.request.load.EntityTypeVersionLoadRequest; +import org.thingsboard.server.common.data.sync.vc.request.load.SingleEntityVersionLoadRequest; +import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadConfig; +import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.sync.ie.EntitiesExportImportService; +import org.thingsboard.server.service.sync.ie.exporting.ExportableEntitiesService; +import org.thingsboard.server.service.sync.ie.importing.impl.MissingEntityException; +import org.thingsboard.server.service.sync.vc.autocommit.TbAutoCommitSettingsService; +import org.thingsboard.server.service.sync.vc.data.CommitGitRequest; +import org.thingsboard.server.service.sync.vc.data.ComplexEntitiesExportCtx; +import org.thingsboard.server.service.sync.vc.data.EntitiesExportCtx; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; +import org.thingsboard.server.service.sync.vc.data.EntityTypeExportCtx; +import org.thingsboard.server.service.sync.vc.data.ReimportTask; +import org.thingsboard.server.service.sync.vc.data.SimpleEntitiesExportCtx; +import org.thingsboard.server.service.sync.vc.repository.TbRepositorySettingsService; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.google.common.util.concurrent.Futures.transform; + +@Service +@TbCoreComponent +@RequiredArgsConstructor +@Slf4j +public class DefaultEntitiesVersionControlService implements EntitiesVersionControlService { + + private final TbRepositorySettingsService repositorySettingsService; + private final TbAutoCommitSettingsService autoCommitSettingsService; + private final GitVersionControlQueueService gitServiceQueue; + private final EntitiesExportImportService exportImportService; + private final ExportableEntitiesService exportableEntitiesService; + private final TbNotificationEntityService entityNotificationService; + private final TransactionTemplate transactionTemplate; + private final TbTransactionalCache taskCache; + + private ListeningExecutorService executor; + + @Value("${vc.thread_pool_size:4}") + private int threadPoolSize; + + @PostConstruct + public void init() { + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(threadPoolSize, DefaultEntitiesVersionControlService.class)); + } + + @PreDestroy + public void shutdown() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @SuppressWarnings("UnstableApiUsage") + @Override + public ListenableFuture saveEntitiesVersion(User user, VersionCreateRequest request) throws Exception { + var pendingCommit = gitServiceQueue.prepareCommit(user, request); + DonAsynchron.withCallback(pendingCommit, commit -> { + cachePut(commit.getTxId(), new VersionCreationResult()); + try { + EntitiesExportCtx theCtx; + switch (request.getType()) { + case SINGLE_ENTITY: { + var ctx = new SimpleEntitiesExportCtx(user, commit, (SingleEntityVersionCreateRequest) request); + handleSingleEntityRequest(ctx); + theCtx = ctx; + break; + } + case COMPLEX: { + var ctx = new ComplexEntitiesExportCtx(user, commit, (ComplexVersionCreateRequest) request); + handleComplexRequest(ctx); + theCtx = ctx; + break; + } + default: + throw new RuntimeException("Unsupported request type: " + request.getType()); + } + var resultFuture = Futures.transformAsync(Futures.allAsList(theCtx.getFutures()), f -> gitServiceQueue.push(commit), executor); + DonAsynchron.withCallback(resultFuture, result -> cachePut(commit.getTxId(), result), e -> processCommitError(user, request, commit, e), executor); + } catch (Exception e) { + processCommitError(user, request, commit, e); + } + }, t -> log.debug("[{}] Failed to prepare the commit: {}", user.getId(), request, t)); + + return transform(pendingCommit, CommitGitRequest::getTxId, MoreExecutors.directExecutor()); + } + + @Override + public VersionCreationResult getVersionCreateStatus(User user, UUID requestId) throws ThingsboardException { + return getStatus(user, requestId, VersionControlTaskCacheEntry::getExportResult); + } + + @Override + public VersionLoadResult getVersionLoadStatus(User user, UUID requestId) throws ThingsboardException { + return getStatus(user, requestId, VersionControlTaskCacheEntry::getImportResult); + } + + private T getStatus(User user, UUID requestId, Function getter) throws ThingsboardException { + var cacheEntry = taskCache.get(requestId); + if (cacheEntry == null || cacheEntry.get() == null) { + log.debug("[{}] No cache record: {}", requestId, cacheEntry); + throw new ThingsboardException(ThingsboardErrorCode.ITEM_NOT_FOUND); + } else { + var entry = cacheEntry.get(); + log.debug("[{}] Cache get: {}", requestId, entry); + var result = getter.apply(entry); + if (result == null) { + throw new ThingsboardException(ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } else { + return result; + } + } + } + + private void handleSingleEntityRequest(SimpleEntitiesExportCtx ctx) throws Exception { + ctx.add(saveEntityData(ctx, ctx.getRequest().getEntityId())); + } + + private void handleComplexRequest(ComplexEntitiesExportCtx parentCtx) { + ComplexVersionCreateRequest request = parentCtx.getRequest(); + request.getEntityTypes().forEach((entityType, config) -> { + EntityTypeExportCtx ctx = new EntityTypeExportCtx(parentCtx, config, request.getSyncStrategy(), entityType); + if (ctx.isOverwrite()) { + ctx.add(gitServiceQueue.deleteAll(ctx.getCommit(), entityType)); + } + + if (config.isAllEntities()) { + DaoUtil.processInBatches(pageLink -> exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink) + , 100, entity -> { + try { + ctx.add(saveEntityData(ctx, entity.getId())); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } else { + for (UUID entityId : config.getEntityIds()) { + try { + ctx.add(saveEntityData(ctx, EntityIdFactory.getByTypeAndUuid(entityType, entityId))); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + }); + } + + private ListenableFuture saveEntityData(EntitiesExportCtx ctx, EntityId entityId) throws Exception { + EntityExportData> entityData = exportImportService.exportEntity(ctx, entityId); + return gitServiceQueue.addToCommit(ctx.getCommit(), entityData); + } + + @Override + public ListenableFuture> listEntityVersions(TenantId tenantId, String branch, EntityId externalId, PageLink pageLink) throws Exception { + return gitServiceQueue.listVersions(tenantId, branch, externalId, pageLink); + } + + @Override + public ListenableFuture> listEntityTypeVersions(TenantId tenantId, String branch, EntityType entityType, PageLink pageLink) throws Exception { + return gitServiceQueue.listVersions(tenantId, branch, entityType, pageLink); + } + + @Override + public ListenableFuture> listVersions(TenantId tenantId, String branch, PageLink pageLink) throws Exception { + return gitServiceQueue.listVersions(tenantId, branch, pageLink); + } + + @Override + public ListenableFuture> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType) throws Exception { + return gitServiceQueue.listEntitiesAtVersion(tenantId, versionId, entityType); + } + + @Override + public ListenableFuture> listAllEntitiesAtVersion(TenantId tenantId, String versionId) throws Exception { + return gitServiceQueue.listEntitiesAtVersion(tenantId, versionId); + } + + @SuppressWarnings({"UnstableApiUsage", "rawtypes"}) + @Override + public UUID loadEntitiesVersion(User user, VersionLoadRequest request) throws Exception { + EntitiesImportCtx ctx = new EntitiesImportCtx(UUID.randomUUID(), user, request.getVersionId()); + cachePut(ctx.getRequestId(), VersionLoadResult.empty()); + switch (request.getType()) { + case SINGLE_ENTITY: { + SingleEntityVersionLoadRequest versionLoadRequest = (SingleEntityVersionLoadRequest) request; + VersionLoadConfig config = versionLoadRequest.getConfig(); + ListenableFuture future = gitServiceQueue.getEntity(user.getTenantId(), request.getVersionId(), versionLoadRequest.getExternalEntityId()); + DonAsynchron.withCallback(future, + entityData -> doInTemplate(ctx, request, c -> loadSingleEntity(c, config, entityData)), + e -> processLoadError(ctx, e), executor); + break; + } + case ENTITY_TYPE: { + EntityTypeVersionLoadRequest versionLoadRequest = (EntityTypeVersionLoadRequest) request; + executor.submit(() -> doInTemplate(ctx, request, c -> loadMultipleEntities(c, versionLoadRequest))); + break; + } + default: + throw new IllegalArgumentException("Unsupported version load request"); + } + + return ctx.getRequestId(); + } + + private VersionLoadResult doInTemplate(EntitiesImportCtx ctx, VersionLoadRequest request, Function function) { + try { + VersionLoadResult result = transactionTemplate.execute(status -> function.apply(ctx)); + for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) { + throwingRunnable.run(); + } + result.setDone(true); + return cachePut(ctx.getRequestId(), result); + } catch (LoadEntityException e) { + return cachePut(ctx.getRequestId(), onError(e.getExternalId(), e.getCause())); + } catch (Exception e) { + log.info("[{}] Failed to process request [{}] due to: ", ctx.getTenantId(), request, e); + return cachePut(ctx.getRequestId(), VersionLoadResult.error(EntityLoadError.runtimeError(e))); + } + } + + private VersionLoadResult loadSingleEntity(EntitiesImportCtx ctx, VersionLoadConfig config, EntityExportData entityData) { + try { + ctx.setSettings(EntityImportSettings.builder() + .updateRelations(config.isLoadRelations()) + .saveAttributes(config.isLoadAttributes()) + .saveCredentials(config.isLoadCredentials()) + .findExistingByName(false) + .build()); + ctx.setFinalImportAttempt(true); + EntityImportResult importResult = exportImportService.importEntity(ctx, entityData); + + exportImportService.saveReferencesAndRelations(ctx); + + return VersionLoadResult.success(EntityTypeLoadResult.builder() + .entityType(importResult.getEntityType()) + .created(importResult.getOldEntity() == null ? 1 : 0) + .updated(importResult.getOldEntity() != null ? 1 : 0) + .deleted(0) + .build()); + } catch (Exception e) { + throw new LoadEntityException(entityData.getExternalId(), e); + } + } + + @SneakyThrows + private VersionLoadResult loadMultipleEntities(EntitiesImportCtx ctx, EntityTypeVersionLoadRequest request) { + var sw = TbStopWatch.create("before"); + + List entityTypes = request.getEntityTypes().keySet().stream() + .sorted(exportImportService.getEntityTypeComparatorForImport()).collect(Collectors.toList()); + for (EntityType entityType : entityTypes) { + log.debug("[{}] Loading {} entities", ctx.getTenantId(), entityType); + sw.startNew("Entities " + entityType.name()); + ctx.setSettings(getEntityImportSettings(request, entityType)); + importEntities(ctx, entityType); + persistToCache(ctx); + } + + sw.startNew("Reimport"); + reimport(ctx); + persistToCache(ctx); + + sw.startNew("Remove Others"); + request.getEntityTypes().keySet().stream() + .filter(entityType -> request.getEntityTypes().get(entityType).isRemoveOtherEntities()) + .sorted(exportImportService.getEntityTypeComparatorForImport().reversed()) + .forEach(entityType -> removeOtherEntities(ctx, entityType)); + persistToCache(ctx); + + sw.startNew("References and Relations"); + exportImportService.saveReferencesAndRelations(ctx); + + sw.stop(); + for (var task : sw.getTaskInfo()) { + log.info("[{}] Executed: {} in {}ms", ctx.getTenantId(), task.getTaskName(), task.getTimeMillis()); + } + log.info("[{}] Total time: {}ms", ctx.getTenantId(), sw.getTotalTimeMillis()); + return VersionLoadResult.success(new ArrayList<>(ctx.getResults().values())); + } + + private EntityImportSettings getEntityImportSettings(EntityTypeVersionLoadRequest request, EntityType entityType) { + var config = request.getEntityTypes().get(entityType); + return EntityImportSettings.builder() + .updateRelations(config.isLoadRelations()) + .saveAttributes(config.isLoadAttributes()) + .saveCredentials(config.isLoadCredentials()) + .findExistingByName(config.isFindExistingEntityByName()) + .build(); + } + + @SneakyThrows + @SuppressWarnings({"rawtypes", "unchecked"}) + private void importEntities(EntitiesImportCtx ctx, EntityType entityType) { + int limit = 100; + int offset = 0; + List entityDataList; + do { + try { + entityDataList = gitServiceQueue.getEntities(ctx.getTenantId(), ctx.getVersionId(), entityType, offset, limit).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + log.debug("[{}] Loading {} entities pack ({})", ctx.getTenantId(), entityType, entityDataList.size()); + for (EntityExportData entityData : entityDataList) { + EntityExportData reimportBackup = JacksonUtil.clone(entityData); + EntityImportResult importResult; + try { + importResult = exportImportService.importEntity(ctx, entityData); + } catch (Exception e) { + throw new LoadEntityException(entityData.getExternalId(), e); + } + registerResult(ctx, entityType, importResult); + + if (!importResult.isUpdatedAllExternalIds()) { + ctx.getToReimport().put(entityData.getEntity().getExternalId(), new ReimportTask(reimportBackup, ctx.getSettings())); + continue; + } + ctx.getImportedEntities().computeIfAbsent(entityType, t -> new HashSet<>()) + .add(importResult.getSavedEntity().getId()); + } + log.debug("Imported {} pack for tenant {}", entityType, ctx.getTenantId()); + offset += limit; + } while (entityDataList.size() == limit); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + private void reimport(EntitiesImportCtx ctx) { + ctx.setFinalImportAttempt(true); + ctx.getToReimport().forEach((externalId, task) -> { + try { + EntityExportData entityData = task.getData(); + var settings = task.getSettings(); + ctx.setSettings(settings); + EntityImportResult importResult = exportImportService.importEntity(ctx, entityData); + + ctx.getImportedEntities().computeIfAbsent(externalId.getEntityType(), t -> new HashSet<>()) + .add(importResult.getSavedEntity().getId()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private void removeOtherEntities(EntitiesImportCtx ctx, EntityType entityType) { + DaoUtil.processInBatches(pageLink -> { + return exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink); + }, 100, entity -> { + if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entity.getId())) { + exportableEntitiesService.removeById(ctx.getTenantId(), entity.getId()); + + ctx.addEventCallback(() -> { + entityNotificationService.notifyDeleteEntity(ctx.getTenantId(), entity.getId(), + entity, null, ActionType.DELETED, null, ctx.getUser()); + }); + ctx.registerDeleted(entityType); + } + }); + } + + private VersionLoadResult onError(EntityId externalId, Throwable e) { + return analyze(e, externalId).orElse(VersionLoadResult.error(EntityLoadError.runtimeError(e))); + } + + private Optional analyze(Throwable e, EntityId externalId) { + if (e == null) { + return Optional.empty(); + } else { + if (e instanceof DeviceCredentialsValidationException) { + return Optional.of(VersionLoadResult.error(EntityLoadError.credentialsError(externalId))); + } else if (e instanceof MissingEntityException) { + return Optional.of(VersionLoadResult.error(EntityLoadError.referenceEntityError(externalId, ((MissingEntityException) e).getEntityId()))); + } else { + return analyze(e.getCause(), externalId); + } + } + } + + @Override + public ListenableFuture compareEntityDataToVersion(User user, EntityId entityId, String versionId) throws Exception { + HasId entity = exportableEntitiesService.findEntityByTenantIdAndId(user.getTenantId(), entityId); + if (!(entity instanceof ExportableEntity)) throw new IllegalArgumentException("Unsupported entity type"); + + EntityId externalId = ((ExportableEntity) entity).getExternalId(); + if (externalId == null) externalId = entityId; + + return transform(gitServiceQueue.getEntity(user.getTenantId(), versionId, externalId), + otherVersion -> { + SimpleEntitiesExportCtx ctx = new SimpleEntitiesExportCtx(user, null, null, EntityExportSettings.builder() + .exportRelations(otherVersion.hasRelations()) + .exportAttributes(otherVersion.hasAttributes()) + .exportCredentials(otherVersion.hasCredentials()) + .build()); + EntityExportData currentVersion; + try { + currentVersion = exportImportService.exportEntity(ctx, entityId); + } catch (ThingsboardException e) { + throw new RuntimeException(e); + } + return new EntityDataDiff(currentVersion.sort(), otherVersion.sort()); + }, MoreExecutors.directExecutor()); + } + + @Override + public ListenableFuture getEntityDataInfo(User user, EntityId entityId, String versionId) { + return Futures.transform(gitServiceQueue.getEntity(user.getTenantId(), versionId, entityId), + entity -> new EntityDataInfo(entity.hasRelations(), entity.hasAttributes(), entity.hasCredentials()), MoreExecutors.directExecutor()); + } + + + @Override + public ListenableFuture> listBranches(TenantId tenantId) throws Exception { + return gitServiceQueue.listBranches(tenantId); + } + + @Override + public RepositorySettings getVersionControlSettings(TenantId tenantId) { + return repositorySettingsService.get(tenantId); + } + + @Override + public ListenableFuture saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings) { + var restoredSettings = this.repositorySettingsService.restore(tenantId, versionControlSettings); + try { + var future = gitServiceQueue.initRepository(tenantId, restoredSettings); + return Futures.transform(future, f -> repositorySettingsService.save(tenantId, restoredSettings), MoreExecutors.directExecutor()); + } catch (Exception e) { + log.debug("{} Failed to init repository: {}", tenantId, versionControlSettings, e); + throw new RuntimeException("Failed to init repository!", e); + } + } + + @Override + public ListenableFuture deleteVersionControlSettings(TenantId tenantId) throws Exception { + if (repositorySettingsService.delete(tenantId)) { + return gitServiceQueue.clearRepository(tenantId); + } else { + return Futures.immediateFuture(null); + } + } + + @Override + public ListenableFuture checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws ThingsboardException { + settings = this.repositorySettingsService.restore(tenantId, settings); + try { + return gitServiceQueue.testRepository(tenantId, settings); + } catch (Exception e) { + throw new ThingsboardException(String.format("Unable to access repository: %s", getCauseMessage(e)), + ThingsboardErrorCode.GENERAL); + } + } + + @Override + public ListenableFuture autoCommit(User user, EntityId entityId) throws Exception { + var repositorySettings = repositorySettingsService.get(user.getTenantId()); + if (repositorySettings == null) { + return Futures.immediateFuture(null); + } + var autoCommitSettings = autoCommitSettingsService.get(user.getTenantId()); + if (autoCommitSettings == null) { + return Futures.immediateFuture(null); + } + var entityType = entityId.getEntityType(); + AutoVersionCreateConfig autoCommitConfig = autoCommitSettings.get(entityType); + if (autoCommitConfig == null) { + return Futures.immediateFuture(null); + } + SingleEntityVersionCreateRequest vcr = new SingleEntityVersionCreateRequest(); + var autoCommitBranchName = autoCommitConfig.getBranch(); + if (StringUtils.isEmpty(autoCommitBranchName)) { + autoCommitBranchName = StringUtils.isNotEmpty(repositorySettings.getDefaultBranch()) ? repositorySettings.getDefaultBranch() : "auto-commits"; + } + vcr.setBranch(autoCommitBranchName); + vcr.setVersionName("auto-commit at " + Instant.ofEpochSecond(System.currentTimeMillis() / 1000)); + vcr.setEntityId(entityId); + vcr.setConfig(autoCommitConfig); + return saveEntitiesVersion(user, vcr); + } + + @Override + public ListenableFuture autoCommit(User user, EntityType entityType, List entityIds) throws Exception { + var repositorySettings = repositorySettingsService.get(user.getTenantId()); + if (repositorySettings == null) { + return Futures.immediateFuture(null); + } + var autoCommitSettings = autoCommitSettingsService.get(user.getTenantId()); + if (autoCommitSettings == null) { + return Futures.immediateFuture(null); + } + AutoVersionCreateConfig autoCommitConfig = autoCommitSettings.get(entityType); + if (autoCommitConfig == null) { + return Futures.immediateFuture(null); + } + var autoCommitBranchName = autoCommitConfig.getBranch(); + if (StringUtils.isEmpty(autoCommitBranchName)) { + autoCommitBranchName = StringUtils.isNotEmpty(repositorySettings.getDefaultBranch()) ? repositorySettings.getDefaultBranch() : "auto-commits"; + } + ComplexVersionCreateRequest vcr = new ComplexVersionCreateRequest(); + vcr.setBranch(autoCommitBranchName); + vcr.setVersionName("auto-commit at " + Instant.ofEpochSecond(System.currentTimeMillis() / 1000)); + vcr.setSyncStrategy(SyncStrategy.MERGE); + + EntityTypeVersionCreateConfig vcrConfig = new EntityTypeVersionCreateConfig(); + vcrConfig.setEntityIds(entityIds); + vcr.setEntityTypes(Collections.singletonMap(entityType, vcrConfig)); + return saveEntitiesVersion(user, vcr); + } + + private String getCauseMessage(Exception e) { + String message; + if (e.getCause() != null && StringUtils.isNotEmpty(e.getCause().getMessage())) { + message = e.getCause().getMessage(); + } else { + message = e.getMessage(); + } + return message; + } + + private void registerResult(EntitiesImportCtx ctx, EntityType entityType, EntityImportResult importResult) { + if (importResult.isCreated()) { + ctx.registerResult(entityType, true); + } else if (importResult.isUpdated() || importResult.isUpdatedRelatedEntities()) { + ctx.registerResult(entityType, false); + } + } + + private void processCommitError(User user, VersionCreateRequest request, CommitGitRequest commit, Throwable e) { + log.debug("[{}] Failed to prepare the commit: {}", user.getId(), request, e); + cachePut(commit.getTxId(), new VersionCreationResult(e.getMessage())); + } + + private void processLoadError(EntitiesImportCtx ctx, Throwable e) { + log.debug("[{}] Failed to load the commit: {}", ctx.getRequestId(), ctx.getVersionId(), e); + cachePut(ctx.getRequestId(), VersionLoadResult.error(EntityLoadError.runtimeError(e))); + } + + private void cachePut(UUID requestId, VersionCreationResult result) { + taskCache.put(requestId, VersionControlTaskCacheEntry.newForExport(result)); + } + + private VersionLoadResult cachePut(UUID requestId, VersionLoadResult result) { + log.debug("[{}] Cache put: {}", requestId, result); + taskCache.put(requestId, VersionControlTaskCacheEntry.newForImport(result)); + return result; + } + + private void persistToCache(EntitiesImportCtx ctx) { + cachePut(ctx.getRequestId(), VersionLoadResult.success(new ArrayList<>(ctx.getResults().values()))); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java new file mode 100644 index 0000000000..413980d119 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitVersionControlQueueService.java @@ -0,0 +1,605 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import com.google.protobuf.ByteString; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.CollectionsUtil; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; +import org.thingsboard.server.common.data.sync.vc.EntityVersionsDiff; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.CommitRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntitiesContentRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntityContentRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GenericRepositoryRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ListEntitiesRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ListVersionsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PrepareMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.VersionControlResponseMsg; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueMsgMetadata; +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.data.ClearRepositoryGitRequest; +import org.thingsboard.server.service.sync.vc.data.CommitGitRequest; +import org.thingsboard.server.service.sync.vc.data.ContentsDiffGitRequest; +import org.thingsboard.server.service.sync.vc.data.EntitiesContentGitRequest; +import org.thingsboard.server.service.sync.vc.data.EntityContentGitRequest; +import org.thingsboard.server.service.sync.vc.data.ListBranchesGitRequest; +import org.thingsboard.server.service.sync.vc.data.ListEntitiesGitRequest; +import org.thingsboard.server.service.sync.vc.data.ListVersionsGitRequest; +import org.thingsboard.server.service.sync.vc.data.PendingGitRequest; +import org.thingsboard.server.service.sync.vc.data.VersionsDiffGitRequest; +import org.thingsboard.server.service.sync.vc.data.VoidGitRequest; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; + +@TbCoreComponent +@Service +@Slf4j +public class DefaultGitVersionControlQueueService implements GitVersionControlQueueService { + + private final TbServiceInfoProvider serviceInfoProvider; + private final TbClusterService clusterService; + private final DataDecodingEncodingService encodingService; + private final DefaultEntitiesVersionControlService entitiesVersionControlService; + private final SchedulerComponent scheduler; + + private final Map> pendingRequestMap = new HashMap<>(); + private final Map> chunkedMsgs = new ConcurrentHashMap<>(); + + @Value("${queue.vc.request-timeout:60000}") + private int requestTimeout; + @Value("${queue.vc.msg-chunk-size:500000}") + private int msgChunkSize; + + public DefaultGitVersionControlQueueService(TbServiceInfoProvider serviceInfoProvider, TbClusterService clusterService, + DataDecodingEncodingService encodingService, + @Lazy DefaultEntitiesVersionControlService entitiesVersionControlService, + SchedulerComponent scheduler) { + this.serviceInfoProvider = serviceInfoProvider; + this.clusterService = clusterService; + this.encodingService = encodingService; + this.entitiesVersionControlService = entitiesVersionControlService; + this.scheduler = scheduler; + } + + @Override + public ListenableFuture prepareCommit(User user, VersionCreateRequest request) { + SettableFuture future = SettableFuture.create(); + + CommitGitRequest commit = new CommitGitRequest(user.getTenantId(), request); + registerAndSend(commit, builder -> builder.setCommitRequest( + buildCommitRequest(commit).setPrepareMsg(getCommitPrepareMsg(user, request)).build() + ).build(), wrap(future, commit)); + return future; + } + + @SuppressWarnings("UnstableApiUsage") + @Override + public ListenableFuture addToCommit(CommitGitRequest commit, EntityExportData> entityData) { + String path = getRelativePath(entityData.getEntityType(), entityData.getExternalId()); + String entityDataJson = JacksonUtil.toPrettyString(entityData.sort()); + + Iterable entityDataChunks = StringUtils.split(entityDataJson, msgChunkSize); + String chunkedMsgId = UUID.randomUUID().toString(); + int chunksCount = Iterables.size(entityDataChunks); + + AtomicInteger chunkIndex = new AtomicInteger(); + List> futures = new ArrayList<>(); + entityDataChunks.forEach(chunk -> { + SettableFuture chunkFuture = SettableFuture.create(); + log.trace("[{}] sending chunk {} for 'addToCommit'", chunkedMsgId, chunkIndex.get()); + registerAndSend(commit, builder -> builder.setCommitRequest( + buildCommitRequest(commit).setAddMsg( + TransportProtos.AddMsg.newBuilder() + .setRelativePath(path).setEntityDataJsonChunk(chunk) + .setChunkedMsgId(chunkedMsgId).setChunkIndex(chunkIndex.getAndIncrement()) + .setChunksCount(chunksCount).build() + ).build() + ).build(), wrap(chunkFuture, null)); + futures.add(chunkFuture); + }); + return Futures.transform(Futures.allAsList(futures), r -> { + log.trace("[{}] sent all chunks for 'addToCommit'", chunkedMsgId); + return null; + }, MoreExecutors.directExecutor()); + } + + @Override + public ListenableFuture deleteAll(CommitGitRequest commit, EntityType entityType) { + SettableFuture future = SettableFuture.create(); + + String path = getRelativePath(entityType, null); + + registerAndSend(commit, builder -> builder.setCommitRequest( + buildCommitRequest(commit).setDeleteMsg( + TransportProtos.DeleteMsg.newBuilder().setRelativePath(path).build() + ).build() + ).build(), wrap(future, null)); + + return future; + } + + @Override + public ListenableFuture push(CommitGitRequest commit) { + registerAndSend(commit, builder -> builder.setCommitRequest( + buildCommitRequest(commit).setPushMsg( + TransportProtos.PushMsg.newBuilder().build() + ).build() + ).build(), wrap(commit.getFuture())); + + return commit.getFuture(); + } + + @Override + public ListenableFuture> listVersions(TenantId tenantId, String branch, PageLink pageLink) { + + return listVersions(tenantId, + applyPageLinkParameters( + ListVersionsRequestMsg.newBuilder() + .setBranchName(branch), + pageLink + ).build()); + } + + @Override + public ListenableFuture> listVersions(TenantId tenantId, String branch, EntityType entityType, PageLink pageLink) { + return listVersions(tenantId, + applyPageLinkParameters( + ListVersionsRequestMsg.newBuilder() + .setBranchName(branch) + .setEntityType(entityType.name()), + pageLink + ).build()); + } + + @Override + public ListenableFuture> listVersions(TenantId tenantId, String branch, EntityId entityId, PageLink pageLink) { + return listVersions(tenantId, + applyPageLinkParameters( + ListVersionsRequestMsg.newBuilder() + .setBranchName(branch) + .setEntityType(entityId.getEntityType().name()) + .setEntityIdMSB(entityId.getId().getMostSignificantBits()) + .setEntityIdLSB(entityId.getId().getLeastSignificantBits()), + pageLink + ).build()); + } + + private ListVersionsRequestMsg.Builder applyPageLinkParameters(ListVersionsRequestMsg.Builder builder, PageLink pageLink) { + builder.setPageSize(pageLink.getPageSize()) + .setPage(pageLink.getPage()); + if (pageLink.getTextSearch() != null) { + builder.setTextSearch(pageLink.getTextSearch()); + } + if (pageLink.getSortOrder() != null) { + if (pageLink.getSortOrder().getProperty() != null) { + builder.setSortProperty(pageLink.getSortOrder().getProperty()); + } + if (pageLink.getSortOrder().getDirection() != null) { + builder.setSortDirection(pageLink.getSortOrder().getDirection().name()); + } + } + return builder; + } + + private ListenableFuture> listVersions(TenantId tenantId, ListVersionsRequestMsg requestMsg) { + ListVersionsGitRequest request = new ListVersionsGitRequest(tenantId); + return sendRequest(request, builder -> builder.setListVersionRequest(requestMsg)); + } + + @Override + public ListenableFuture> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType) { + return listEntitiesAtVersion(tenantId, ListEntitiesRequestMsg.newBuilder() + .setVersionId(versionId) + .setEntityType(entityType.name()) + .build()); + } + + @Override + public ListenableFuture> listEntitiesAtVersion(TenantId tenantId, String versionId) { + return listEntitiesAtVersion(tenantId, ListEntitiesRequestMsg.newBuilder() + .setVersionId(versionId) + .build()); + } + + private ListenableFuture> listEntitiesAtVersion(TenantId tenantId, TransportProtos.ListEntitiesRequestMsg requestMsg) { + ListEntitiesGitRequest request = new ListEntitiesGitRequest(tenantId); + return sendRequest(request, builder -> builder.setListEntitiesRequest(requestMsg)); + } + + @Override + public ListenableFuture> listBranches(TenantId tenantId) { + ListBranchesGitRequest request = new ListBranchesGitRequest(tenantId); + return sendRequest(request, builder -> builder.setListBranchesRequest(TransportProtos.ListBranchesRequestMsg.newBuilder().build())); + } + + @Override + public ListenableFuture> getVersionsDiff(TenantId tenantId, EntityType entityType, EntityId externalId, String versionId1, String versionId2) { + String path = entityType != null ? getRelativePath(entityType, externalId) : ""; + VersionsDiffGitRequest request = new VersionsDiffGitRequest(tenantId, path, versionId1, versionId2); + return sendRequest(request, builder -> builder.setVersionsDiffRequest(TransportProtos.VersionsDiffRequestMsg.newBuilder() + .setPath(request.getPath()) + .setVersionId1(request.getVersionId1()) + .setVersionId2(request.getVersionId2()) + .build())); + } + + @Override + @SuppressWarnings("rawtypes") + public ListenableFuture getEntity(TenantId tenantId, String versionId, EntityId entityId) { + EntityContentGitRequest request = new EntityContentGitRequest(tenantId, versionId, entityId); + chunkedMsgs.put(request.getRequestId(), new HashMap<>()); + registerAndSend(request, builder -> builder.setEntityContentRequest(EntityContentRequestMsg.newBuilder() + .setVersionId(versionId) + .setEntityType(entityId.getEntityType().name()) + .setEntityIdMSB(entityId.getId().getMostSignificantBits()) + .setEntityIdLSB(entityId.getId().getLeastSignificantBits())).build() + , wrap(request.getFuture())); + return request.getFuture(); + } + + private void registerAndSend(PendingGitRequest request, + Function enrichFunction, TbQueueCallback callback) { + registerAndSend(request, enrichFunction, null, callback); + } + + private void registerAndSend(PendingGitRequest request, + Function enrichFunction, RepositorySettings settings, TbQueueCallback callback) { + if (!request.getFuture().isDone()) { + pendingRequestMap.putIfAbsent(request.getRequestId(), request); + var requestBody = enrichFunction.apply(newRequestProto(request, settings)); + log.trace("[{}][{}] PUSHING request: {}", request.getTenantId(), request.getRequestId(), requestBody); + clusterService.pushMsgToVersionControl(request.getTenantId(), requestBody, callback); + if (request.getTimeoutTask() == null) { + request.setTimeoutTask(scheduler.schedule(() -> processTimeout(request.getRequestId()), requestTimeout, TimeUnit.MILLISECONDS)); + } + } else { + throw new RuntimeException("Future is already done!"); + } + } + + private ListenableFuture sendRequest(PendingGitRequest request, Consumer enrichFunction) { + registerAndSend(request, builder -> { + enrichFunction.accept(builder); + return builder.build(); + }, wrap(request.getFuture())); + return request.getFuture(); + } + + @Override + @SuppressWarnings("rawtypes") + public ListenableFuture> getEntities(TenantId tenantId, String versionId, EntityType entityType, int offset, int limit) { + EntitiesContentGitRequest request = new EntitiesContentGitRequest(tenantId, versionId, entityType); + chunkedMsgs.put(request.getRequestId(), new HashMap<>()); + registerAndSend(request, builder -> builder.setEntitiesContentRequest(EntitiesContentRequestMsg.newBuilder() + .setVersionId(versionId) + .setEntityType(entityType.name()) + .setOffset(offset) + .setLimit(limit) + ).build() + , wrap(request.getFuture())); + + return request.getFuture(); + } + + @Override + public ListenableFuture initRepository(TenantId tenantId, RepositorySettings settings) { + VoidGitRequest request = new VoidGitRequest(tenantId); + + registerAndSend(request, builder -> builder.setInitRepositoryRequest(GenericRepositoryRequestMsg.newBuilder().build()).build() + , settings, wrap(request.getFuture())); + + return request.getFuture(); + } + + @Override + public ListenableFuture testRepository(TenantId tenantId, RepositorySettings settings) { + VoidGitRequest request = new VoidGitRequest(tenantId); + + registerAndSend(request, builder -> builder + .setTestRepositoryRequest(GenericRepositoryRequestMsg.newBuilder().build()).build() + , settings, wrap(request.getFuture())); + + return request.getFuture(); + } + + @Override + public ListenableFuture clearRepository(TenantId tenantId) { + ClearRepositoryGitRequest request = new ClearRepositoryGitRequest(tenantId); + + registerAndSend(request, builder -> builder.setClearRepositoryRequest(GenericRepositoryRequestMsg.newBuilder().build()).build() + , wrap(request.getFuture())); + + return request.getFuture(); + } + + @Override + public void processResponse(VersionControlResponseMsg vcResponseMsg) { + UUID requestId = new UUID(vcResponseMsg.getRequestIdMSB(), vcResponseMsg.getRequestIdLSB()); + PendingGitRequest request = pendingRequestMap.get(requestId); + if (request == null) { + log.debug("[{}] received stale response: {}", requestId, vcResponseMsg); + return; + } else { + log.debug("[{}] processing response: {}", requestId, vcResponseMsg); + } + var future = request.getFuture(); + boolean completed = true; + if (!StringUtils.isEmpty(vcResponseMsg.getError())) { + future.setException(new RuntimeException(vcResponseMsg.getError())); + } else { + try { + if (vcResponseMsg.hasGenericResponse()) { + future.set(null); + } else if (vcResponseMsg.hasCommitResponse()) { + var commitResponse = vcResponseMsg.getCommitResponse(); + var commitResult = new VersionCreationResult(); + if (commitResponse.getTs() > 0) { + commitResult.setVersion(new EntityVersion(commitResponse.getTs(), commitResponse.getCommitId(), commitResponse.getName(), commitResponse.getAuthor())); + } + commitResult.setAdded(commitResponse.getAdded()); + commitResult.setRemoved(commitResponse.getRemoved()); + commitResult.setModified(commitResponse.getModified()); + commitResult.setDone(true); + ((CommitGitRequest) request).getFuture().set(commitResult); + } else if (vcResponseMsg.hasListBranchesResponse()) { + var listBranchesResponse = vcResponseMsg.getListBranchesResponse(); + ((ListBranchesGitRequest) request).getFuture().set(listBranchesResponse.getBranchesList().stream().map(this::getBranchInfo).collect(Collectors.toList())); + } else if (vcResponseMsg.hasListEntitiesResponse()) { + var listEntitiesResponse = vcResponseMsg.getListEntitiesResponse(); + ((ListEntitiesGitRequest) request).getFuture().set( + listEntitiesResponse.getEntitiesList().stream().map(this::getVersionedEntityInfo).collect(Collectors.toList())); + } else if (vcResponseMsg.hasListVersionsResponse()) { + var listVersionsResponse = vcResponseMsg.getListVersionsResponse(); + ((ListVersionsGitRequest) request).getFuture().set(toPageData(listVersionsResponse)); + } else if (vcResponseMsg.hasEntityContentResponse()) { + TransportProtos.EntityContentResponseMsg responseMsg = vcResponseMsg.getEntityContentResponse(); + log.trace("Received chunk {} for 'getEntity'", responseMsg.getChunkIndex()); + var joined = joinChunks(requestId, responseMsg, 0, 1); + if (joined.isPresent()) { + log.trace("Collected all chunks for 'getEntity'"); + ((EntityContentGitRequest) request).getFuture().set(joined.get().get(0)); + } else { + completed = false; + } + } else if (vcResponseMsg.hasEntitiesContentResponse()) { + TransportProtos.EntitiesContentResponseMsg responseMsg = vcResponseMsg.getEntitiesContentResponse(); + TransportProtos.EntityContentResponseMsg item = responseMsg.getItem(); + if (responseMsg.getItemsCount() > 0) { + var joined = joinChunks(requestId, item, responseMsg.getItemIdx(), responseMsg.getItemsCount()); + if (joined.isPresent()) { + ((EntitiesContentGitRequest) request).getFuture().set(joined.get()); + } else { + completed = false; + } + } else { + ((EntitiesContentGitRequest) request).getFuture().set(Collections.emptyList()); + } + } else if (vcResponseMsg.hasVersionsDiffResponse()) { + TransportProtos.VersionsDiffResponseMsg diffResponse = vcResponseMsg.getVersionsDiffResponse(); + List entityVersionsDiffList = diffResponse.getDiffList().stream() + .map(diff -> EntityVersionsDiff.builder() + .externalId(EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(diff.getEntityType()), + new UUID(diff.getEntityIdMSB(), diff.getEntityIdLSB()))) + .entityDataAtVersion1(StringUtils.isNotEmpty(diff.getEntityDataAtVersion1()) ? + toData(diff.getEntityDataAtVersion1()) : null) + .entityDataAtVersion2(StringUtils.isNotEmpty(diff.getEntityDataAtVersion2()) ? + toData(diff.getEntityDataAtVersion2()) : null) + .rawDiff(diff.getRawDiff()) + .build()) + .collect(Collectors.toList()); + ((VersionsDiffGitRequest) request).getFuture().set(entityVersionsDiffList); + } + } catch (Exception e) { + future.setException(e); + throw e; + } + } + if (completed) { + removePendingRequest(requestId); + } + } + + @SuppressWarnings("rawtypes") + private Optional> joinChunks(UUID requestId, TransportProtos.EntityContentResponseMsg responseMsg, int itemIdx, int expectedMsgCount) { + var chunksMap = chunkedMsgs.get(requestId); + if (chunksMap == null) { + return Optional.empty(); + } + String[] msgChunks = chunksMap.computeIfAbsent(itemIdx, id -> new String[responseMsg.getChunksCount()]); + msgChunks[responseMsg.getChunkIndex()] = responseMsg.getData(); + if (chunksMap.size() == expectedMsgCount && chunksMap.values().stream() + .allMatch(chunks -> CollectionsUtil.countNonNull(chunks) == chunks.length)) { + return Optional.of(chunksMap.entrySet().stream() + .sorted(Comparator.comparingInt(Map.Entry::getKey)).map(Map.Entry::getValue) + .map(chunks -> String.join("", chunks)) + .map(this::toData) + .collect(Collectors.toList())); + } else { + return Optional.empty(); + } + } + + private void processTimeout(UUID requestId) { + PendingGitRequest pendingRequest = removePendingRequest(requestId); + if (pendingRequest != null) { + log.debug("[{}] request timed out ({} ms}", requestId, requestTimeout); + pendingRequest.getFuture().setException(new TimeoutException("Request timed out")); + } + } + + private PendingGitRequest removePendingRequest(UUID requestId) { + PendingGitRequest pendingRequest = pendingRequestMap.remove(requestId); + if (pendingRequest != null && pendingRequest.getTimeoutTask() != null) { + pendingRequest.getTimeoutTask().cancel(true); + pendingRequest.setTimeoutTask(null); + } + chunkedMsgs.remove(requestId); + return pendingRequest; + } + + private PageData toPageData(TransportProtos.ListVersionsResponseMsg listVersionsResponse) { + var listVersions = listVersionsResponse.getVersionsList().stream().map(this::getEntityVersion).collect(Collectors.toList()); + return new PageData<>(listVersions, listVersionsResponse.getTotalPages(), listVersionsResponse.getTotalElements(), listVersionsResponse.getHasNext()); + } + + private EntityVersion getEntityVersion(TransportProtos.EntityVersionProto proto) { + return new EntityVersion(proto.getTs(), proto.getId(), proto.getName(), proto.getAuthor()); + } + + private VersionedEntityInfo getVersionedEntityInfo(TransportProtos.VersionedEntityInfoProto proto) { + return new VersionedEntityInfo(EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB()))); + } + + private BranchInfo getBranchInfo(TransportProtos.BranchInfoProto proto) { + return new BranchInfo(proto.getName(), proto.getIsDefault()); + } + + @SuppressWarnings("rawtypes") + @SneakyThrows + private EntityExportData toData(String data) { + return JacksonUtil.fromString(data, EntityExportData.class); + } + + //The future will be completed when the corresponding result arrives from kafka + private static TbQueueCallback wrap(SettableFuture future) { + return new TbQueueCallback() { + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + } + + @Override + public void onFailure(Throwable t) { + future.setException(t); + } + }; + } + + //The future will be completed when the request is successfully sent to kafka + private TbQueueCallback wrap(SettableFuture future, T value) { + return new TbQueueCallback() { + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + future.set(value); + } + + @Override + public void onFailure(Throwable t) { + future.setException(t); + } + }; + } + + private static String getRelativePath(EntityType entityType, EntityId entityId) { + String path = entityType.name().toLowerCase(); + if (entityId != null) { + path += "/" + entityId + ".json"; + } + return path; + } + + private static PrepareMsg getCommitPrepareMsg(User user, VersionCreateRequest request) { + return PrepareMsg.newBuilder().setCommitMsg(request.getVersionName()) + .setBranchName(request.getBranch()).setAuthorName(getAuthorName(user)).setAuthorEmail(user.getEmail()).build(); + } + + private static String getAuthorName(User user) { + List parts = new ArrayList<>(); + if (StringUtils.isNotBlank(user.getFirstName())) { + parts.add(user.getFirstName()); + } + if (StringUtils.isNotBlank(user.getLastName())) { + parts.add(user.getLastName()); + } + if (parts.isEmpty()) { + parts.add(user.getName()); + } + return String.join(" ", parts); + } + + private ToVersionControlServiceMsg.Builder newRequestProto(PendingGitRequest request, RepositorySettings settings) { + var tenantId = request.getTenantId(); + var requestId = request.getRequestId(); + var builder = ToVersionControlServiceMsg.newBuilder() + .setNodeId(serviceInfoProvider.getServiceId()) + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setRequestIdMSB(requestId.getMostSignificantBits()) + .setRequestIdLSB(requestId.getLeastSignificantBits()); + RepositorySettings vcSettings = settings; + if (vcSettings == null && request.requiresSettings()) { + vcSettings = entitiesVersionControlService.getVersionControlSettings(tenantId); + } + if (vcSettings != null) { + builder.setVcSettings(ByteString.copyFrom(encodingService.encode(vcSettings))); + } else if (request.requiresSettings()) { + throw new RuntimeException("No entity version control settings provisioned!"); + } + return builder; + } + + private CommitRequestMsg.Builder buildCommitRequest(CommitGitRequest commit) { + return CommitRequestMsg.newBuilder().setTxId(commit.getTxId().toString()); + } +} + diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java new file mode 100644 index 0000000000..8a2410e932 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/EntitiesVersionControlService.java @@ -0,0 +1,78 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.EntityDataDiff; +import org.thingsboard.server.common.data.sync.vc.EntityDataInfo; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; +import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest; + +import java.util.List; +import java.util.UUID; + +public interface EntitiesVersionControlService { + + ListenableFuture saveEntitiesVersion(User user, VersionCreateRequest request) throws Exception; + + VersionCreationResult getVersionCreateStatus(User user, UUID requestId) throws ThingsboardException; + + ListenableFuture> listEntityVersions(TenantId tenantId, String branch, EntityId externalId, PageLink pageLink) throws Exception; + + ListenableFuture> listEntityTypeVersions(TenantId tenantId, String branch, EntityType entityType, PageLink pageLink) throws Exception; + + ListenableFuture> listVersions(TenantId tenantId, String branch, PageLink pageLink) throws Exception; + + ListenableFuture> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType) throws Exception; + + ListenableFuture> listAllEntitiesAtVersion(TenantId tenantId, String versionId) throws Exception; + + UUID loadEntitiesVersion(User user, VersionLoadRequest request) throws Exception; + + VersionLoadResult getVersionLoadStatus(User user, UUID requestId) throws ThingsboardException; + + ListenableFuture compareEntityDataToVersion(User user, EntityId entityId, String versionId) throws Exception; + + ListenableFuture> listBranches(TenantId tenantId) throws Exception; + + RepositorySettings getVersionControlSettings(TenantId tenantId); + + ListenableFuture saveVersionControlSettings(TenantId tenantId, RepositorySettings versionControlSettings); + + ListenableFuture deleteVersionControlSettings(TenantId tenantId) throws Exception; + + ListenableFuture checkVersionControlAccess(TenantId tenantId, RepositorySettings settings) throws Exception; + + ListenableFuture autoCommit(User user, EntityId entityId) throws Exception; + + ListenableFuture autoCommit(User user, EntityType entityType, List entityIds) throws Exception; + + ListenableFuture getEntityDataInfo(User user, EntityId entityId, String versionId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java new file mode 100644 index 0000000000..feba7db76d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/GitVersionControlQueueService.java @@ -0,0 +1,74 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; +import org.thingsboard.server.common.data.sync.vc.EntityVersionsDiff; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; +import org.thingsboard.server.gen.transport.TransportProtos.VersionControlResponseMsg; +import org.thingsboard.server.service.sync.vc.data.CommitGitRequest; + +import java.util.List; + +public interface GitVersionControlQueueService { + + ListenableFuture prepareCommit(User user, VersionCreateRequest request); + + ListenableFuture addToCommit(CommitGitRequest commit, EntityExportData> entityData); + + ListenableFuture deleteAll(CommitGitRequest pendingCommit, EntityType entityType); + + ListenableFuture push(CommitGitRequest commit); + + ListenableFuture> listVersions(TenantId tenantId, String branch, PageLink pageLink); + + ListenableFuture> listVersions(TenantId tenantId, String branch, EntityType entityType, PageLink pageLink); + + ListenableFuture> listVersions(TenantId tenantId, String branch, EntityId entityId, PageLink pageLink); + + ListenableFuture> listEntitiesAtVersion(TenantId tenantId, String versionId, EntityType entityType); + + ListenableFuture> listEntitiesAtVersion(TenantId tenantId, String versionId); + + ListenableFuture> listBranches(TenantId tenantId); + + ListenableFuture getEntity(TenantId tenantId, String versionId, EntityId entityId); + + ListenableFuture> getEntities(TenantId tenantId, String versionId, EntityType entityType, int offset, int limit); + + ListenableFuture> getVersionsDiff(TenantId tenantId, EntityType entityType, EntityId externalId, String versionId1, String versionId2); + + ListenableFuture initRepository(TenantId tenantId, RepositorySettings settings); + + ListenableFuture testRepository(TenantId tenantId, RepositorySettings settings); + + ListenableFuture clearRepository(TenantId tenantId); + + void processResponse(VersionControlResponseMsg vcResponseMsg); +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java new file mode 100644 index 0000000000..13d8280046 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/LoadEntityException.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import lombok.Getter; +import org.thingsboard.server.common.data.id.EntityId; + +@SuppressWarnings("rawtypes") +public class LoadEntityException extends RuntimeException { + + private static final long serialVersionUID = -1749719992370409504L; + @Getter + private final EntityId externalId; + + public LoadEntityException(EntityId externalId, Throwable cause) { + super(cause); + this.externalId = externalId; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java new file mode 100644 index 0000000000..3a674eece0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/TbAbstractVersionControlSettingsService.java @@ -0,0 +1,80 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.settings.AdminSettingsService; + +import java.io.Serializable; + +public abstract class TbAbstractVersionControlSettingsService { + + private final String settingsKey; + private final AdminSettingsService adminSettingsService; + private final TbTransactionalCache cache; + private final Class clazz; + + public TbAbstractVersionControlSettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache cache, Class clazz, String settingsKey) { + this.adminSettingsService = adminSettingsService; + this.cache = cache; + this.clazz = clazz; + this.settingsKey = settingsKey; + } + + public T get(TenantId tenantId) { + return cache.getAndPutInTransaction(tenantId, () -> { + AdminSettings adminSettings = adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, settingsKey); + if (adminSettings != null) { + try { + return JacksonUtil.convertValue(adminSettings.getJsonValue(), clazz); + } catch (Exception e) { + throw new RuntimeException("Failed to load " + settingsKey + " settings!", e); + } + } + return null; + }, true); + } + + public T save(TenantId tenantId, T settings) { + AdminSettings adminSettings = adminSettingsService.findAdminSettingsByTenantIdAndKey(tenantId, settingsKey); + if (adminSettings == null) { + adminSettings = new AdminSettings(); + adminSettings.setKey(settingsKey); + adminSettings.setTenantId(tenantId); + } + adminSettings.setJsonValue(JacksonUtil.valueToTree(settings)); + AdminSettings savedAdminSettings = adminSettingsService.saveAdminSettings(tenantId, adminSettings); + T savedSettings; + try { + savedSettings = JacksonUtil.convertValue(savedAdminSettings.getJsonValue(), clazz); + } catch (Exception e) { + throw new RuntimeException("Failed to load auto commit settings!", e); + } + //API calls to adminSettingsService are not in transaction, so we can simply evict the cache. + cache.evict(tenantId); + return savedSettings; + } + + public boolean delete(TenantId tenantId) { + boolean result = adminSettingsService.deleteAdminSettingsByTenantIdAndKey(tenantId, settingsKey); + cache.evict(tenantId); + return result; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.java new file mode 100644 index 0000000000..dcf3ef3a70 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCacheEntry.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.service.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionLoadResult; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +public class VersionControlTaskCacheEntry implements Serializable { + + private static final long serialVersionUID = -7875992200801588119L; + + private VersionCreationResult exportResult; + private VersionLoadResult importResult; + + public static VersionControlTaskCacheEntry newForExport(VersionCreationResult result) { + return new VersionControlTaskCacheEntry(result, null); + } + + public static VersionControlTaskCacheEntry newForImport(VersionLoadResult result) { + return new VersionControlTaskCacheEntry(null, result); + } + + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.java new file mode 100644 index 0000000000..e58ceba8c5 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskCaffeineCache.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.service.sync.vc; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.UUID; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("VersionControlTaskCache") +public class VersionControlTaskCaffeineCache extends CaffeineTbTransactionalCache { + + public VersionControlTaskCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.VERSION_CONTROL_TASK_CACHE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.java new file mode 100644 index 0000000000..6215744f7a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlTaskRedisCache.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.service.sync.vc; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; + +import java.util.UUID; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("VersionControlTaskCache") +public class VersionControlTaskRedisCache extends RedisTbTransactionalCache { + + public VersionControlTaskRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.VERSION_CONTROL_TASK_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java new file mode 100644 index 0000000000..47b9e5f5d2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsCaffeineCache.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.autocommit; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("AutoCommitSettingsCache") +public class AutoCommitSettingsCaffeineCache extends CaffeineTbTransactionalCache { + + public AutoCommitSettingsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.AUTO_COMMIT_SETTINGS_CACHE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.java new file mode 100644 index 0000000000..e3c918818c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/AutoCommitSettingsRedisCache.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.service.sync.vc.autocommit; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("AutoCommitSettingsCache") +public class AutoCommitSettingsRedisCache extends RedisTbTransactionalCache { + + public AutoCommitSettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.AUTO_COMMIT_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.java new file mode 100644 index 0000000000..b6e8d45ec2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/DefaultTbAutoCommitSettingsService.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.service.sync.vc.autocommit; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.TbAbstractVersionControlSettingsService; + +@Service +@TbCoreComponent +public class DefaultTbAutoCommitSettingsService extends TbAbstractVersionControlSettingsService implements TbAutoCommitSettingsService { + + public static final String SETTINGS_KEY = "autoCommitSettings"; + + public DefaultTbAutoCommitSettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache cache) { + super(adminSettingsService, cache, AutoCommitSettings.class, SETTINGS_KEY); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.java new file mode 100644 index 0000000000..51978481e1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/autocommit/TbAutoCommitSettingsService.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.service.sync.vc.autocommit; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; + +public interface TbAutoCommitSettingsService { + + AutoCommitSettings get(TenantId tenantId); + + AutoCommitSettings save(TenantId tenantId, AutoCommitSettings settings); + + boolean delete(TenantId tenantId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ClearRepositoryGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ClearRepositoryGitRequest.java new file mode 100644 index 0000000000..09245eb895 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ClearRepositoryGitRequest.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.service.sync.vc.data; + +import org.thingsboard.server.common.data.id.TenantId; + +public class ClearRepositoryGitRequest extends VoidGitRequest { + + public ClearRepositoryGitRequest(TenantId tenantId) { + super(tenantId); + } + + public boolean requiresSettings() { + return false; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/CommitGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/CommitGitRequest.java new file mode 100644 index 0000000000..a989439d10 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/CommitGitRequest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import lombok.Getter; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; + +import java.util.UUID; + +public class CommitGitRequest extends PendingGitRequest { + + @Getter + private final UUID txId; + private final VersionCreateRequest request; + + public CommitGitRequest(TenantId tenantId, VersionCreateRequest request) { + super(tenantId); + this.txId = UUID.randomUUID(); + this.request = request; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ComplexEntitiesExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ComplexEntitiesExportCtx.java new file mode 100644 index 0000000000..e8577b049d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ComplexEntitiesExportCtx.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.service.sync.vc.data; + +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; +import org.thingsboard.server.common.data.sync.vc.request.create.ComplexVersionCreateRequest; + +import java.util.HashMap; +import java.util.Map; + +public class ComplexEntitiesExportCtx extends EntitiesExportCtx { + + private final Map settings = new HashMap<>(); + + public ComplexEntitiesExportCtx(User user, CommitGitRequest commit, ComplexVersionCreateRequest request) { + super(user, commit, request); + request.getEntityTypes().forEach((type, config) -> settings.put(type, buildExportSettings(config))); + } + + public EntityExportSettings getSettings(EntityType entityType) { + return settings.get(entityType); + } + + @Override + public EntityExportSettings getSettings() { + throw new RuntimeException("Not implemented. Use EntityTypeExportCtx instead!"); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ContentsDiffGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ContentsDiffGitRequest.java new file mode 100644 index 0000000000..7c3fb7a600 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ContentsDiffGitRequest.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import lombok.Getter; +import org.thingsboard.server.common.data.id.TenantId; + +@Getter +public class ContentsDiffGitRequest extends PendingGitRequest { + + private final String content1; + private final String content2; + + public ContentsDiffGitRequest(TenantId tenantId, String content1, String content2) { + super(tenantId); + this.content1 = content1; + this.content2 = content2; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesContentGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesContentGitRequest.java new file mode 100644 index 0000000000..ad653edd0c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesContentGitRequest.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.service.sync.vc.data; + +import lombok.Getter; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; + +import java.util.List; + +@Getter +public class EntitiesContentGitRequest extends PendingGitRequest> { + + private final String versionId; + private final EntityType entityType; + + public EntitiesContentGitRequest(TenantId tenantId, String versionId, EntityType entityType) { + super(tenantId); + this.versionId = versionId; + this.entityType = entityType; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesExportCtx.java new file mode 100644 index 0000000000..4c89c91271 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesExportCtx.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.service.sync.vc.data; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateConfig; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Slf4j +@Data +public abstract class EntitiesExportCtx { + + protected final User user; + protected final CommitGitRequest commit; + protected final R request; + private final List> futures; + private final Map externalIdMap; + + public EntitiesExportCtx(User user, CommitGitRequest commit, R request) { + this.user = user; + this.commit = commit; + this.request = request; + this.futures = new ArrayList<>(); + this.externalIdMap = new HashMap<>(); + } + + protected EntitiesExportCtx(EntitiesExportCtx other) { + this.user = other.getUser(); + this.commit = other.getCommit(); + this.request = other.getRequest(); + this.futures = other.getFutures(); + this.externalIdMap = other.getExternalIdMap(); + } + + public void add(ListenableFuture future) { + futures.add(future); + } + + public TenantId getTenantId() { + return user.getTenantId(); + } + + protected static EntityExportSettings buildExportSettings(VersionCreateConfig config) { + return EntityExportSettings.builder() + .exportRelations(config.isSaveRelations()) + .exportAttributes(config.isSaveAttributes()) + .exportCredentials(config.isSaveCredentials()) + .build(); + } + + public abstract EntityExportSettings getSettings(); + + @SuppressWarnings("unchecked") + public ID getExternalId(ID internalId) { + var result = externalIdMap.get(internalId); + log.debug("[{}][{}] Local cache {} for id", internalId.getEntityType(), internalId.getId(), result != null ? "hit" : "miss"); + return (ID) result; + } + + public void putExternalId(EntityId internalId, EntityId externalId) { + log.debug("[{}][{}] Local cache put: {}", internalId.getEntityType(), internalId.getId(), externalId); + externalIdMap.put(internalId, externalId != null ? externalId : internalId); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java new file mode 100644 index 0000000000..fddc91883e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntitiesImportCtx.java @@ -0,0 +1,143 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; +import org.thingsboard.server.common.data.sync.vc.EntityTypeLoadResult; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +@Slf4j +@Data +public class EntitiesImportCtx { + + private final UUID requestId; + private final User user; + private final String versionId; + + private final Map results = new HashMap<>(); + private final Map> importedEntities = new HashMap<>(); + private final Map toReimport = new HashMap<>(); + private final Map referenceCallbacks = new HashMap<>(); + private final List eventCallbacks = new ArrayList<>(); + private final Map externalToInternalIdMap = new HashMap<>(); + private final Set notFoundIds = new HashSet<>(); + + private final Set relations = new LinkedHashSet<>(); + + private boolean finalImportAttempt = false; + private EntityImportSettings settings; + private EntityImportResult currentImportResult; + + public EntitiesImportCtx(UUID requestId, User user, String versionId) { + this(requestId, user, versionId, null); + } + + public EntitiesImportCtx(UUID requestId, User user, String versionId, EntityImportSettings settings) { + this.requestId = requestId; + this.user = user; + this.versionId = versionId; + this.settings = settings; + } + + public TenantId getTenantId() { + return user.getTenantId(); + } + + public boolean isFindExistingByName() { + return getSettings().isFindExistingByName(); + } + + public boolean isUpdateRelations() { + return getSettings().isUpdateRelations(); + } + + public boolean isSaveAttributes() { + return getSettings().isSaveAttributes(); + } + + public boolean isSaveCredentials() { + return getSettings().isSaveCredentials(); + } + + public EntityId getInternalId(EntityId externalId) { + var result = externalToInternalIdMap.get(externalId); + log.debug("[{}][{}] Local cache {} for id", externalId.getEntityType(), externalId.getId(), result != null ? "hit" : "miss"); + return result; + } + + public void putInternalId(EntityId externalId, EntityId internalId) { + log.debug("[{}][{}] Local cache put: {}", externalId.getEntityType(), externalId.getId(), internalId); + externalToInternalIdMap.put(externalId, internalId); + } + + public void registerResult(EntityType entityType, boolean created) { + EntityTypeLoadResult result = results.computeIfAbsent(entityType, EntityTypeLoadResult::new); + if (created) { + result.setCreated(result.getCreated() + 1); + } else { + result.setUpdated(result.getUpdated() + 1); + } + } + + public void registerDeleted(EntityType entityType) { + EntityTypeLoadResult result = results.computeIfAbsent(entityType, EntityTypeLoadResult::new); + result.setDeleted(result.getDeleted() + 1); + } + + public void addRelations(Collection values) { + relations.addAll(values); + } + + public void addReferenceCallback(EntityId externalId, ThrowingRunnable tr) { + if (tr != null) { + referenceCallbacks.put(externalId, tr); + } + } + + public void addEventCallback(ThrowingRunnable tr) { + if (tr != null) { + eventCallbacks.add(tr); + } + } + + public void registerNotFound(EntityId externalId) { + notFoundIds.add(externalId); + } + + public boolean isNotFound(EntityId externalId) { + return notFoundIds.contains(externalId); + } + + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityContentGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityContentGitRequest.java new file mode 100644 index 0000000000..6a1d60a065 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityContentGitRequest.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import lombok.Getter; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; + +@Getter +public class EntityContentGitRequest extends PendingGitRequest { + + private final String versionId; + private final EntityId entityId; + + public EntityContentGitRequest(TenantId tenantId, String versionId, EntityId entityId) { + super(tenantId); + this.versionId = versionId; + this.entityId = entityId; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityTypeExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityTypeExportCtx.java new file mode 100644 index 0000000000..80d90590a1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/EntityTypeExportCtx.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import lombok.Getter; +import org.apache.commons.lang3.ObjectUtils; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; +import org.thingsboard.server.common.data.sync.vc.request.create.EntityTypeVersionCreateConfig; +import org.thingsboard.server.common.data.sync.vc.request.create.SyncStrategy; +import org.thingsboard.server.common.data.sync.vc.request.create.VersionCreateRequest; + +public class EntityTypeExportCtx extends EntitiesExportCtx { + + @Getter + private final EntityType entityType; + @Getter + private final boolean overwrite; + @Getter + private final EntityExportSettings settings; + + public EntityTypeExportCtx(EntitiesExportCtx parent, EntityTypeVersionCreateConfig config, SyncStrategy defaultSyncStrategy, EntityType entityType) { + super(parent); + this.entityType = entityType; + this.settings = EntityExportSettings.builder() + .exportRelations(config.isSaveRelations()) + .exportAttributes(config.isSaveAttributes()) + .exportCredentials(config.isSaveCredentials()) + .build(); + this.overwrite = ObjectUtils.defaultIfNull(config.getSyncStrategy(), defaultSyncStrategy) == SyncStrategy.OVERWRITE; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListBranchesGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListBranchesGitRequest.java new file mode 100644 index 0000000000..4d89efa14d --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListBranchesGitRequest.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; + +import java.util.List; + +public class ListBranchesGitRequest extends PendingGitRequest> { + + public ListBranchesGitRequest(TenantId tenantId) { + super(tenantId); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListEntitiesGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListEntitiesGitRequest.java new file mode 100644 index 0000000000..3fc531735e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListEntitiesGitRequest.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; + +import java.util.List; + +public class ListEntitiesGitRequest extends PendingGitRequest> { + + public ListEntitiesGitRequest(TenantId tenantId) { + super(tenantId); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListVersionsGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListVersionsGitRequest.java new file mode 100644 index 0000000000..d40a589646 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ListVersionsGitRequest.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; + +public class ListVersionsGitRequest extends PendingGitRequest> { + + public ListVersionsGitRequest(TenantId tenantId) { + super(tenantId); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/PendingGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/PendingGitRequest.java new file mode 100644 index 0000000000..b34806a6ca --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/PendingGitRequest.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import com.google.common.util.concurrent.SettableFuture; +import lombok.Getter; +import lombok.Setter; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.UUID; +import java.util.concurrent.ScheduledFuture; + +@Getter +public class PendingGitRequest { + + private final long createdTime; + private final UUID requestId; + private final TenantId tenantId; + private final SettableFuture future; + @Setter + private ScheduledFuture timeoutTask; + + public PendingGitRequest(TenantId tenantId) { + this.createdTime = System.currentTimeMillis(); + this.requestId = UUID.randomUUID(); + this.tenantId = tenantId; + this.future = SettableFuture.create(); + } + + public boolean requiresSettings() { + return true; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java new file mode 100644 index 0000000000..97432adbb8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/ReimportTask.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import lombok.Data; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; + +@Data +public class ReimportTask { + + private final EntityExportData data; + private final EntityImportSettings settings; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/SimpleEntitiesExportCtx.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/SimpleEntitiesExportCtx.java new file mode 100644 index 0000000000..4cf5d93765 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/SimpleEntitiesExportCtx.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.service.sync.vc.data; + +import lombok.Getter; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; +import org.thingsboard.server.common.data.sync.vc.request.create.SingleEntityVersionCreateRequest; + +public class SimpleEntitiesExportCtx extends EntitiesExportCtx { + + @Getter + private final EntityExportSettings settings; + + public SimpleEntitiesExportCtx(User user, CommitGitRequest commit, SingleEntityVersionCreateRequest request) { + this(user, commit, request, request != null ? buildExportSettings(request.getConfig()) : null); + } + + public SimpleEntitiesExportCtx(User user, CommitGitRequest commit, SingleEntityVersionCreateRequest request, EntityExportSettings settings) { + super(user, commit, request); + this.settings = settings; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionsDiffGitRequest.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionsDiffGitRequest.java new file mode 100644 index 0000000000..e73706609f --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VersionsDiffGitRequest.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.data; + +import lombok.Getter; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.EntityVersionsDiff; + +import java.util.List; + +@Getter +public class VersionsDiffGitRequest extends PendingGitRequest> { + + private final String path; + private final String versionId1; + private final String versionId2; + + public VersionsDiffGitRequest(TenantId tenantId, String path, String versionId1, String versionId2) { + super(tenantId); + this.path = path; + this.versionId1 = versionId1; + this.versionId2 = versionId2; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/widgetsBundle/TbWidgetsBundleService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VoidGitRequest.java similarity index 68% rename from application/src/main/java/org/thingsboard/server/service/entitiy/widgetsBundle/TbWidgetsBundleService.java rename to application/src/main/java/org/thingsboard/server/service/sync/vc/data/VoidGitRequest.java index 7672db1a71..c48bfa7a03 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/widgetsBundle/TbWidgetsBundleService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/data/VoidGitRequest.java @@ -13,10 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.entitiy.widgetsBundle; +package org.thingsboard.server.service.sync.vc.data; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.service.entitiy.SimpleTbEntityService; +import org.thingsboard.server.common.data.id.TenantId; + +public class VoidGitRequest extends PendingGitRequest { + + public VoidGitRequest(TenantId tenantId) { + super(tenantId); + } -public interface TbWidgetsBundleService extends SimpleTbEntityService { } diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java new file mode 100644 index 0000000000..555490bd06 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/DefaultTbRepositorySettingsService.java @@ -0,0 +1,63 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.repository; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod; +import org.thingsboard.server.dao.settings.AdminSettingsService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.sync.vc.TbAbstractVersionControlSettingsService; + +@Service +@TbCoreComponent +public class DefaultTbRepositorySettingsService extends TbAbstractVersionControlSettingsService implements TbRepositorySettingsService { + + public static final String SETTINGS_KEY = "entitiesVersionControl"; + + public DefaultTbRepositorySettingsService(AdminSettingsService adminSettingsService, TbTransactionalCache cache) { + super(adminSettingsService, cache, RepositorySettings.class, SETTINGS_KEY); + } + + @Override + public RepositorySettings restore(TenantId tenantId, RepositorySettings settings) { + RepositorySettings storedSettings = get(tenantId); + if (storedSettings != null) { + RepositoryAuthMethod authMethod = settings.getAuthMethod(); + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(authMethod) && settings.getPassword() == null) { + settings.setPassword(storedSettings.getPassword()); + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(authMethod) && settings.getPrivateKey() == null) { + settings.setPrivateKey(storedSettings.getPrivateKey()); + if (settings.getPrivateKeyPassword() == null) { + settings.setPrivateKeyPassword(storedSettings.getPrivateKeyPassword()); + } + } + } + return settings; + } + + @Override + public RepositorySettings get(TenantId tenantId) { + RepositorySettings settings = super.get(tenantId); + if (settings != null) { + settings = new RepositorySettings(settings); + } + return settings; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java new file mode 100644 index 0000000000..60b7f50e12 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsCaffeineCache.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc.repository; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("RepositorySettingsCache") +public class RepositorySettingsCaffeineCache extends CaffeineTbTransactionalCache { + + public RepositorySettingsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.REPOSITORY_SETTINGS_CACHE); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.java new file mode 100644 index 0000000000..a49ace3ec2 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/RepositorySettingsRedisCache.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.service.sync.vc.repository; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("RepositorySettingsCache") +public class RepositorySettingsRedisCache extends RedisTbTransactionalCache { + + public RepositorySettingsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.REPOSITORY_SETTINGS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.java new file mode 100644 index 0000000000..946d06c87c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/repository/TbRepositorySettingsService.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.service.sync.vc.repository; + +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; + +public interface TbRepositorySettingsService { + + RepositorySettings restore(TenantId tenantId, RepositorySettings versionControlSettings); + + RepositorySettings get(TenantId tenantId); + + RepositorySettings save(TenantId tenantId, RepositorySettings versionControlSettings); + + boolean delete(TenantId tenantId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java index 9b67628aa3..5eb258c2a6 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AttributeData.java @@ -32,17 +32,17 @@ public class AttributeData implements Comparable{ this.value = value; } - @ApiModelProperty(position = 1, value = "Timestamp last updated attribute, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 1, value = "Timestamp last updated attribute, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public long getLastUpdateTs() { return lastUpdateTs; } - @ApiModelProperty(position = 2, value = "String representing attribute key", example = "active", readOnly = true) + @ApiModelProperty(position = 2, value = "String representing attribute key", example = "active", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public String getKey() { return key; } - @ApiModelProperty(position = 3, value = "Object representing value of attribute key", example = "false", readOnly = true) + @ApiModelProperty(position = 3, value = "Object representing value of attribute key", example = "false", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public Object getValue() { return value; } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 38275c09b6..5571c4badb 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.cluster.TbClusterService; @@ -43,12 +44,12 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; +import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import javax.annotation.Nullable; @@ -75,7 +76,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private final AttributesService attrService; private final TimeseriesService tsService; - private final EntityViewService entityViewService; + private final TbEntityViewService tbEntityViewService; private final TbApiUsageClient apiUsageClient; private final TbApiUsageStateService apiUsageStateService; @@ -83,7 +84,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer public DefaultTelemetrySubscriptionService(AttributesService attrService, TimeseriesService tsService, - EntityViewService entityViewService, + @Lazy TbEntityViewService tbEntityViewService, TbClusterService clusterService, PartitionService partitionService, TbApiUsageClient apiUsageClient, @@ -91,7 +92,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; - this.entityViewService = entityViewService; + this.tbEntityViewService = tbEntityViewService; this.apiUsageClient = apiUsageClient; this.apiUsageStateService = apiUsageStateService; } @@ -182,11 +183,11 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer addMainCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) { - Futures.addCallback(this.entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId), + Futures.addCallback(this.tbEntityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId), new FutureCallback>() { @Override public void onSuccess(@Nullable List result) { - if (result != null) { + if (result != null && !result.isEmpty()) { Map> tsMap = new HashMap<>(); for (TsKvEntry entry : ts) { tsMap.computeIfAbsent(entry.getKey(), s -> new ArrayList<>()).add(entry); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java index 5a50a2ea22..0551d7e114 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java @@ -42,7 +42,9 @@ import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.util.TenantRateLimitException; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; @@ -138,14 +140,8 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @Autowired private TbServiceInfoProvider serviceInfoProvider; - @Value("${server.ws.limits.max_subscriptions_per_tenant:0}") - private int maxSubscriptionsPerTenant; - @Value("${server.ws.limits.max_subscriptions_per_customer:0}") - private int maxSubscriptionsPerCustomer; - @Value("${server.ws.limits.max_subscriptions_per_regular_user:0}") - private int maxSubscriptionsPerRegularUser; - @Value("${server.ws.limits.max_subscriptions_per_public_user:0}") - private int maxSubscriptionsPerPublicUser; + @Autowired + private TbTenantProfileCache tenantProfileCache; @Value("${server.ws.ping_timeout:30000}") private long pingTimeout; @@ -320,44 +316,50 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } private void processSessionClose(TelemetryWebSocketSessionRef sessionRef) { - String sessionId = "[" + sessionRef.getSessionId() + "]"; - if (maxSubscriptionsPerTenant > 0) { - Set tenantSubscriptions = tenantSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); - synchronized (tenantSubscriptions) { - tenantSubscriptions.removeIf(subId -> subId.startsWith(sessionId)); - } - } - if (sessionRef.getSecurityCtx().isCustomerUser()) { - if (maxSubscriptionsPerCustomer > 0) { - Set customerSessions = customerSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); - synchronized (customerSessions) { - customerSessions.removeIf(subId -> subId.startsWith(sessionId)); + var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); + if (tenantProfileConfiguration != null) { + String sessionId = "[" + sessionRef.getSessionId() + "]"; + + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerTenant() > 0) { + Set tenantSubscriptions = tenantSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); + synchronized (tenantSubscriptions) { + tenantSubscriptions.removeIf(subId -> subId.startsWith(sessionId)); } } - if (maxSubscriptionsPerRegularUser > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { - Set regularUserSessions = regularUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); - synchronized (regularUserSessions) { - regularUserSessions.removeIf(subId -> subId.startsWith(sessionId)); + if (sessionRef.getSecurityCtx().isCustomerUser()) { + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerCustomer() > 0) { + Set customerSessions = customerSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); + synchronized (customerSessions) { + customerSessions.removeIf(subId -> subId.startsWith(sessionId)); + } } - } - if (maxSubscriptionsPerPublicUser > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { - Set publicUserSessions = publicUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); - synchronized (publicUserSessions) { - publicUserSessions.removeIf(subId -> subId.startsWith(sessionId)); + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + Set regularUserSessions = regularUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); + synchronized (regularUserSessions) { + regularUserSessions.removeIf(subId -> subId.startsWith(sessionId)); + } + } + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + Set publicUserSessions = publicUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); + synchronized (publicUserSessions) { + publicUserSessions.removeIf(subId -> subId.startsWith(sessionId)); + } } } } } private boolean processSubscription(TelemetryWebSocketSessionRef sessionRef, SubscriptionCmd cmd) { + var tenantProfileConfiguration = (DefaultTenantProfileConfiguration) tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultProfileConfiguration(); + String subId = "[" + sessionRef.getSessionId() + "]:[" + cmd.getCmdId() + "]"; try { - if (maxSubscriptionsPerTenant > 0) { + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerTenant() > 0) { Set tenantSubscriptions = tenantSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); synchronized (tenantSubscriptions) { if (cmd.isUnsubscribe()) { tenantSubscriptions.remove(subId); - } else if (tenantSubscriptions.size() < maxSubscriptionsPerTenant) { + } else if (tenantSubscriptions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerTenant()) { tenantSubscriptions.add(subId); } else { log.info("[{}][{}][{}] Failed to start subscription. Max tenant subscriptions limit reached" @@ -369,12 +371,12 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } if (sessionRef.getSecurityCtx().isCustomerUser()) { - if (maxSubscriptionsPerCustomer > 0) { + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerCustomer() > 0) { Set customerSessions = customerSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); synchronized (customerSessions) { if (cmd.isUnsubscribe()) { customerSessions.remove(subId); - } else if (customerSessions.size() < maxSubscriptionsPerCustomer) { + } else if (customerSessions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerCustomer()) { customerSessions.add(subId); } else { log.info("[{}][{}][{}] Failed to start subscription. Max customer subscriptions limit reached" @@ -384,10 +386,10 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } } - if (maxSubscriptionsPerRegularUser > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { Set regularUserSessions = regularUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); synchronized (regularUserSessions) { - if (regularUserSessions.size() < maxSubscriptionsPerRegularUser) { + if (regularUserSessions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerRegularUser()) { regularUserSessions.add(subId); } else { log.info("[{}][{}][{}] Failed to start subscription. Max regular user subscriptions limit reached" @@ -397,10 +399,10 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi } } } - if (maxSubscriptionsPerPublicUser > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { + if (tenantProfileConfiguration.getMaxWsSubscriptionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { Set publicUserSessions = publicUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); synchronized (publicUserSessions) { - if (publicUserSessions.size() < maxSubscriptionsPerPublicUser) { + if (publicUserSessions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerPublicUser()) { publicUserSessions.add(subId); } else { log.info("[{}][{}][{}] Failed to start subscription. Max public user subscriptions limit reached" diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java index e48561a965..56d1785d45 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/TsData.java @@ -30,12 +30,12 @@ public class TsData implements Comparable{ this.value = value; } - @ApiModelProperty(position = 1, value = "Timestamp last updated timeseries, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 1, value = "Timestamp last updated timeseries, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public long getTs() { return ts; } - @ApiModelProperty(position = 2, value = "Object representing value of timeseries key", example = "20", readOnly = true) + @ApiModelProperty(position = 2, value = "Object representing value of timeseries key", example = "20", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public Object getValue() { return value; } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 6c4adfc5ae..f8b42ecc1e 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -65,7 +65,7 @@ import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceProvisionService; import org.thingsboard.server.dao.device.DeviceService; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java index 4e3abd8e68..e178055706 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java @@ -32,7 +32,7 @@ import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.action.EntityActionService; @@ -50,7 +50,7 @@ public class AlarmsCleanUpService { @Value("${sql.ttl.alarms.removal_batch_size}") private Integer removalBatchSize; - private final TenantDao tenantDao; + private final TenantService tenantService; private final AlarmDao alarmDao; private final AlarmService alarmService; private final RelationService relationService; @@ -64,7 +64,7 @@ public class AlarmsCleanUpService { PageLink removalBatchRequest = new PageLink(removalBatchSize, 0 ); PageData tenantsIds; do { - tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest); + tenantsIds = tenantService.findTenantsIds(tenantsBatchRequest); for (TenantId tenantId : tenantsIds.getData()) { if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { continue; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java index 877987d6f0..1c21cc6029 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java @@ -27,7 +27,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.rpc.RpcDao; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; -import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -43,7 +43,7 @@ public class RpcCleanUpService { @Value("${sql.ttl.rpc.enabled}") private boolean ttlTaskExecutionEnabled; - private final TenantDao tenantDao; + private final TenantService tenantService; private final PartitionService partitionService; private final TbTenantProfileCache tenantProfileCache; private final RpcDao rpcDao; @@ -54,7 +54,7 @@ public class RpcCleanUpService { PageLink tenantsBatchRequest = new PageLink(10_000, 0); PageData tenantsIds; do { - tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest); + tenantsIds = tenantService.findTenantsIds(tenantsBatchRequest); for (TenantId tenantId : tenantsIds.getData()) { if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { continue; diff --git a/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java b/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java new file mode 100644 index 0000000000..af03c72004 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/springfox/SpringfoxHandlerProviderBeanPostProcessor.java @@ -0,0 +1,60 @@ +/** + * 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.springfox; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.stereotype.Component; +import org.springframework.util.ReflectionUtils; +import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping; +import org.thingsboard.server.queue.util.TbCoreComponent; +import springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider; + +import java.lang.reflect.Field; +import java.util.List; +import java.util.stream.Collectors; + +@Component +//TODO: remove after fixing issue https://github.com/springfox/springfox/issues/3462 or after migration from springfox to springdoc +public class SpringfoxHandlerProviderBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + if (bean instanceof WebMvcRequestHandlerProvider) { + customizeSpringfoxHandlerMappings(getHandlerMappings(bean)); + } + return bean; + } + + private void customizeSpringfoxHandlerMappings(List mappings) { + List copy = mappings.stream() + .filter(mapping -> mapping.getPatternParser() == null) + .collect(Collectors.toList()); + mappings.clear(); + mappings.addAll(copy); + } + + @SuppressWarnings("unchecked") + private List getHandlerMappings(Object bean) { + try { + Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings"); + field.setAccessible(true); + return (List) field.get(bean); + } catch (IllegalArgumentException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml index c68d484cd5..941bd7d278 100644 --- a/application/src/main/resources/logback.xml +++ b/application/src/main/resources/logback.xml @@ -25,23 +25,31 @@ - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - - + + diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 9f6f3099ef..17b996934e 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -28,7 +28,7 @@ server: # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) - type: "${SSL_CREDENTIALS_TYPE:PEM}" + type: "${SSL_CREDENTIALS_TYPE:PEM}" # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) @@ -58,18 +58,6 @@ server: send_timeout: "${TB_SERVER_WS_SEND_TIMEOUT:5000}" # recommended timeout >= 30 seconds. Platform will attempt to send 'ping' request 3 times within the timeout ping_timeout: "${TB_SERVER_WS_PING_TIMEOUT:30000}" - limits: - # Limit the amount of sessions and subscriptions available on each server. Put values to zero to disable particular limitation - max_sessions_per_tenant: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_TENANT:0}" - max_sessions_per_customer: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_CUSTOMER:0}" - max_sessions_per_regular_user: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_REGULAR_USER:0}" - max_sessions_per_public_user: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SESSIONS_PER_PUBLIC_USER:0}" - max_queue_per_ws_session: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_QUEUE_PER_WS_SESSION:500}" - max_subscriptions_per_tenant: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_TENANT:0}" - max_subscriptions_per_customer: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_CUSTOMER:0}" - max_subscriptions_per_regular_user: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_REGULAR_USER:0}" - max_subscriptions_per_public_user: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_SUBSCRIPTIONS_PER_PUBLIC_USER:0}" - max_updates_per_session: "${TB_SERVER_WS_TENANT_RATE_LIMITS_MAX_UPDATES_PER_SESSION:300:1,3000:60}" dynamic_page_link: refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}" refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}" @@ -78,13 +66,6 @@ server: max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}" max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}" rest: - limits: - tenant: - enabled: "${TB_SERVER_REST_LIMITS_TENANT_ENABLED:false}" - configuration: "${TB_SERVER_REST_LIMITS_TENANT_CONFIGURATION:100:1,2000:60}" - customer: - enabled: "${TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED:false}" - configuration: "${TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION:50:1,1000:60}" server_side_rpc: # Minimum value of the server side RPC timeout. May override value provided in the REST API call. # Since 2.5 migration to queues, the RPC delay depends on the size of the pending messages in the queue, @@ -167,7 +148,7 @@ ui: # Help parameters help: # Base url for UI help assets - base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.4}" + base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4}" database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records @@ -255,8 +236,6 @@ cassandra: # log one of cassandra queries with specified frequency (0 - logging is disabled) print_queries_freq: "${CASSANDRA_QUERY_PRINT_FREQ:0}" tenant_rate_limits: - enabled: "${CASSANDRA_QUERY_TENANT_RATE_LIMITS_ENABLED:false}" - configuration: "${CASSANDRA_QUERY_TENANT_RATE_LIMITS_CONFIGURATION:1000:1,30000:60}" print_tenant_names: "${CASSANDRA_QUERY_TENANT_RATE_LIMITS_PRINT_TENANT_NAMES:false}" # SQL configuration parameters @@ -287,7 +266,6 @@ sql: batch_size: "${SQL_EDGE_EVENTS_BATCH_SIZE:1000}" batch_max_delay: "${SQL_EDGE_EVENTS_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_EDGE_EVENTS_BATCH_STATS_PRINT_MS:10000}" - batch_threads: "${SQL_EDGE_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks batch_sort: "${SQL_BATCH_SORT:false}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert @@ -348,7 +326,9 @@ actors: # Specify thread pool size for javascript executor service js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:50}" # Specify thread pool size for mail sender executor service - mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:50}" + mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:40}" + # Specify thread pool size for password reset emails + mail_password_reset_thread_pool_size: "${ACTORS_RULE_MAIL_PASSWORD_RESET_THREAD_POOL_SIZE:10}" # Specify thread pool size for sms sender executor service sms_thread_pool_size: "${ACTORS_RULE_SMS_THREAD_POOL_SIZE:50}" # Whether to allow usage of system mail service for rules @@ -415,15 +395,22 @@ cache: tenantProfiles: timeToLiveInMinutes: "${CACHE_SPECS_TENANT_PROFILES_TTL:1440}" maxSize: "${CACHE_SPECS_TENANT_PROFILES_MAX_SIZE:10000}" + tenants: + timeToLiveInMinutes: "${CACHE_SPECS_TENANTS_TTL:1440}" + maxSize: "${CACHE_SPECS_TENANTS_MAX_SIZE:10000}" + tenantsExist: + # environment variables are intentionally the same as in 'tenants' cache to be equal. + timeToLiveInMinutes: "${CACHE_SPECS_TENANTS_TTL:1440}" + maxSize: "${CACHE_SPECS_TENANTS_MAX_SIZE:10000}" deviceProfiles: timeToLiveInMinutes: "${CACHE_SPECS_DEVICE_PROFILES_TTL:1440}" maxSize: "${CACHE_SPECS_DEVICE_PROFILES_MAX_SIZE:10000}" attributes: timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" - tokensOutdatageTime: - timeToLiveInMinutes: "${CACHE_SPECS_TOKENS_OUTDATAGE_TIME_TTL:20000}" - maxSize: "${CACHE_SPECS_TOKENS_OUTDATAGE_TIME_MAX_SIZE:10000}" + usersUpdateTime: + timeToLiveInMinutes: "${CACHE_SPECS_USERS_UPDATE_TIME_TTL:20000}" + maxSize: "${CACHE_SPECS_USERS_UPDATE_TIME_MAX_SIZE:10000}" otaPackages: timeToLiveInMinutes: "${CACHE_SPECS_OTA_PACKAGES_TTL:60}" maxSize: "${CACHE_SPECS_OTA_PACKAGES_MAX_SIZE:10}" @@ -433,9 +420,21 @@ cache: edges: timeToLiveInMinutes: "${CACHE_SPECS_EDGES_TTL:1440}" maxSize: "${CACHE_SPECS_EDGES_MAX_SIZE:10000}" + repositorySettings: + timeToLiveInMinutes: "${CACHE_SPECS_REPOSITORY_SETTINGS_TTL:1440}" + maxSize: "${CACHE_SPECS_REPOSITORY_SETTINGS_MAX_SIZE:10000}" + autoCommitSettings: + timeToLiveInMinutes: "${CACHE_SPECS_AUTO_COMMIT_SETTINGS_TTL:1440}" + maxSize: "${CACHE_SPECS_AUTO_COMMIT_SETTINGS_MAX_SIZE:10000}" twoFaVerificationCodes: timeToLiveInMinutes: "${CACHE_SPECS_TWO_FA_VERIFICATION_CODES_TTL:60}" maxSize: "${CACHE_SPECS_TWO_FA_VERIFICATION_CODES_MAX_SIZE:100000}" + versionControlTask: + timeToLiveInMinutes: "${CACHE_SPECS_VERSION_CONTROL_TASK_TTL:5}" + maxSize: "${CACHE_SPECS_VERSION_CONTROL_TASK_MAX_SIZE:100000}" + +#Disable this because it is not required. +spring.data.redis.repositories.enabled: false redis: # standalone or cluster @@ -508,6 +507,9 @@ spring.mvc.cors: # The default timeout for asynchronous requests in milliseconds spring.mvc.async.request-timeout: "${SPRING_MVC_ASYNC_REQUEST_TIMEOUT:30000}" +# For endpoints matching in Swagger +spring.mvc.pathmatch.matching-strategy: "ANT_PATH_MATCHER" + # spring serve gzip compressed static resources spring.resources.chain: compressed: "true" @@ -545,10 +547,6 @@ spring: audit-log: # Enable/disable audit log functionality. enabled: "${AUDIT_LOG_ENABLED:true}" - # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS - by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}" - # Number of days as history period if startTime and endTime are not specified - default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" # Logging levels per each entity type. # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations) logging-level: @@ -675,7 +673,7 @@ transport: # Server SSL credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) - type: "${MQTT_SSL_CREDENTIALS_TYPE:PEM}" + type: "${MQTT_SSL_CREDENTIALS_TYPE:PEM}" # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) @@ -718,7 +716,7 @@ transport: # Server DTLS credentials credentials: # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) - type: "${COAP_DTLS_CREDENTIALS_TYPE:PEM}" + type: "${COAP_DTLS_CREDENTIALS_TYPE:PEM}" # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) @@ -796,7 +794,7 @@ transport: # Whether to enable LWM2M bootstrap server X509 Certificate/RPK support enabled: "${LWM2M_BS_CREDENTIALS_ENABLED:false}" # Server credentials type (PEM - pem certificate file; KEYSTORE - java keystore) - type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}" + type: "${LWM2M_BS_CREDENTIALS_TYPE:PEM}" # PEM server credentials pem: # Path to the server certificate file (holds server certificate or certificate chain, may include server private key) @@ -823,7 +821,7 @@ transport: # Whether to load X509 trust certificates enabled: "${LWM2M_TRUST_CREDENTIALS_ENABLED:false}" # Trust certificates store type (PEM - pem certificates file; KEYSTORE - java keystore) - type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}" + type: "${LWM2M_TRUST_CREDENTIALS_TYPE:PEM}" # PEM certificates pem: # Path to the certificates file (holds trust certificates) @@ -847,8 +845,8 @@ transport: psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}" paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}" network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside -# - key: "PROTOCOL_STAGE_THREAD_COUNT" -# value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}" + # - key: "PROTOCOL_STAGE_THREAD_COUNT" + # value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}" snmp: enabled: "${SNMP_ENABLED:true}" response_processing: @@ -931,9 +929,12 @@ queue: tb_ota_package: - key: max.poll.records value: "${TB_QUEUE_KAFKA_OTA_MAX_POLL_RECORDS:10}" -# tb_rule_engine.sq: -# - key: max.poll.records -# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}" + tb_version_control: + - key: max.poll.interval.ms + value: "${TB_QUEUE_KAFKA_VC_MAX_POLL_INTERVAL_MS:600000}" + # tb_rule_engine.sq: + # - key: max.poll.records + # value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}" other: # In this section you can specify custom parameters for Kafka consumer/producer and expose the env variables to configure outside - key: "request.timeout.ms" # refer to https://docs.confluent.io/platform/current/installation/configuration/producer-configs.html#producerconfigs_request.timeout.ms value: "${TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS:30000}" # (30 seconds) @@ -942,10 +943,11 @@ queue: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}" notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" ota-updates: "${TB_QUEUE_KAFKA_OTA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}" + version-control: "${TB_QUEUE_KAFKA_VC_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}" consumer-stats: enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}" @@ -962,6 +964,8 @@ queue: transport-api: "${TB_QUEUE_AWS_SQS_TA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" notifications: "${TB_QUEUE_AWS_SQS_NOTIFICATIONS_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" js-executor: "${TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" + ota-updates: "${TB_QUEUE_AWS_SQS_OTA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" + version-control: "${TB_QUEUE_AWS_SQS_VC_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" # VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds pubsub: project_id: "${TB_QUEUE_PUBSUB_PROJECT_ID:YOUR_PROJECT_ID}" @@ -974,6 +978,7 @@ queue: transport-api: "${TB_QUEUE_PUBSUB_TA_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" notifications: "${TB_QUEUE_PUBSUB_NOTIFICATIONS_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" js-executor: "${TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" + version-control: "${TB_QUEUE_PUBSUB_VC_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" service_bus: namespace_name: "${TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME:YOUR_NAMESPACE_NAME}" sas_key_name: "${TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME:YOUR_SAS_KEY_NAME}" @@ -985,6 +990,7 @@ queue: transport-api: "${TB_QUEUE_SERVICE_BUS_TA_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" notifications: "${TB_QUEUE_SERVICE_BUS_NOTIFICATIONS_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" js-executor: "${TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" + version-control: "${TB_QUEUE_SERVICE_BUS_VC_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" rabbitmq: exchange_name: "${TB_QUEUE_RABBIT_MQ_EXCHANGE_NAME:}" host: "${TB_QUEUE_RABBIT_MQ_HOST:localhost}" @@ -1001,6 +1007,7 @@ queue: transport-api: "${TB_QUEUE_RABBIT_MQ_TA_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" notifications: "${TB_QUEUE_RABBIT_MQ_NOTIFICATIONS_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" + version-control: "${TB_QUEUE_RABBIT_MQ_VC_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" partitions: hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256 transport_api: @@ -1024,6 +1031,13 @@ queue: stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}" + vc: + topic: "${TB_QUEUE_VC_TOPIC:tb_version_control}" + partitions: "${TB_QUEUE_VC_PARTITIONS:10}" + poll-interval: "${TB_QUEUE_VC_INTERVAL_MS:25}" + pack-processing-timeout: "${TB_QUEUE_VC_PACK_PROCESSING_TIMEOUT_MS:60000}" + request-timeout: "${TB_QUEUE_VC_REQUEST_TIMEOUT:60000}" + msg-chunk-size: "${TB_QUEUE_VC_MSG_CHUNK_SIZE:500000}" js: # JS Eval request topic request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}" @@ -1035,6 +1049,8 @@ queue: max_eval_requests_timeout: "${REMOTE_JS_MAX_EVAL_REQUEST_TIMEOUT:60000}" # JS max request timeout max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" + # JS execution max request timeout + max_exec_requests_timeout: "${REMOTE_JS_MAX_EXEC_REQUEST_TIMEOUT:2000}" # JS response poll interval response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" rule-engine: @@ -1117,6 +1133,14 @@ metrics: # Metrics percentiles returned by actuator for timer metrics. List of double values (divided by ,). percentiles: "${METRICS_TIMER_PERCENTILES:0.5}" +vc: + # Pool size for handling export tasks + thread_pool_size: "${TB_VC_POOL_SIZE:2}" + git: + # Pool size for handling the git IO operations + io_pool_size: "${TB_VC_GIT_POOL_SIZE:3}" + repositories-folder: "${TB_VC_GIT_REPOSITORIES_FOLDER:${java.io.tmpdir}/repositories}" + management: endpoints: web: diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java index d908ac8d2a..d204750e32 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java @@ -16,23 +16,34 @@ package org.thingsboard.server.controller; import lombok.extern.slf4j.Slf4j; +import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.model.ModelConstants; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; +import java.util.Objects; +import java.util.stream.Collectors; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -47,90 +58,275 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { @SpyBean protected AuditLogService auditLogService; + protected final String msgErrorPermission = "You don't have permission to perform this operation!"; + protected final String msgErrorShouldBeSpecified = "should be specified"; + protected final String msgErrorNotFound = "Requested item wasn't found!"; + + protected void testNotifyEntityAllOneTime(HasName entity, EntityId entityId, EntityId originatorId, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, Object... additionalInfo) { - testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(originatorId, tenantId); + int cntTime = 1; + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); Mockito.reset(tbClusterService, auditLogService); } - protected void testNotifyEntityDeleteOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, - TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, Object... additionalInfo) { - testNotificationMsgToEdgeServiceNever(entityId); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(entityId, tenantId); - testBroadcastEntityStateChangeEventOneTime(entityId, tenantId); + protected void testNotifyEntityAllOneTimeRelation(EntityRelation relation, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, Object... additionalInfo) { + int cntTime = 1; + Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), + Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), + Mockito.eq(edgeTypeByActionType(actionType))); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(relation.getTo()); + ArgumentMatcher matcherEntityClassEquals = Objects::isNull; + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, + extractMatcherAdditionalInfo(additionalInfo)); + testPushMsgToRuleEngineNever(relation.getTo()); + matcherOriginatorId = argument -> argument.equals(relation.getFrom()); + testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, + extractMatcherAdditionalInfo(additionalInfo)); + testPushMsgToRuleEngineNever(relation.getFrom()); + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyEntityAllManyRelation(EntityRelation relation, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime) { + Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), + Mockito.isNull(), Mockito.isNull(), Mockito.any(), Mockito.eq(EdgeEventType.RELATION), + Mockito.eq(edgeTypeByActionType(actionType))); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(relation.getFrom().getClass()); + ArgumentMatcher matcherEntityClassEquals = Objects::isNull; + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, + userName, actionType, cntTime * 2, 1); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, new Tenant(), cntTime); + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(HasName entity, EntityId entityId, EntityId originatorId, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, Object... additionalInfo) { + int cntTime = 1; + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime); + testLogEntityActionEntityEqClass(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); Mockito.reset(tbClusterService, auditLogService); } - protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId, ActionType actionType) { - testSendNotificationMsgToEdgeServiceOneTime(entityId, tenantId, actionType); + + protected void testNotifyEntityNeverMsgToEdgeServiceOneTime(HasName entity, EntityId entityId, TenantId tenantId, + ActionType actionType) { + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, 1); testLogEntityActionNever(entityId, entity); testPushMsgToRuleEngineNever(entityId); Mockito.reset(tbClusterService, auditLogService); } protected void testNotifyEntityOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, - TenantId tenantId, CustomerId customerId, UserId userId, String userName, - ActionType actionType, Object... additionalInfo) { - testNotificationMsgToEdgeServiceNever(entityId); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(originatorId, tenantId); + TenantId tenantId, CustomerId customerId, UserId userId, + String userName, ActionType actionType, Object... additionalInfo) { + int cntTime = 1; + testNotificationMsgToEdgeServiceNeverWithActionType(entityId, actionType); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + if (ActionType.RELATIONS_DELETED.equals(actionType)) { + testPushMsgToRuleEngineNever(originatorId); + } else { + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); + } + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyManyEntityManyTimeMsgToEdgeServiceNever(HasName entity, HasName originator, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, Object... additionalInfo) { + EntityId entityId = createEntityId_NULL_UUID(entity); + EntityId originatorId = createEntityId_NULL_UUID(originator); + testNotificationMsgToEdgeServiceNeverWithActionType(entityId, actionType); + ArgumentMatcher matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, + extractMatcherAdditionalInfo(additionalInfo)); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(HasName entity, HasName originator, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, ActionType actionTypeEdge, + int cntTime, int cntTimeEdge, int cntTimeRuleEngine, Object... additionalInfo) { + EntityId originatorId = createEntityId_NULL_UUID(originator); + testSendNotificationMsgToEdgeServiceTimeEntityEqAny(tenantId, actionTypeEdge, cntTimeEdge); + ArgumentMatcher matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfo(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, + extractMatcherAdditionalInfoClass(additionalInfo)); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTimeRuleEngine); + } + + protected void testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(HasName entity, HasName originator, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, ActionType actionTypeEdge, int cntTime, int cntTimeEdge, int cntAdditionalInfo) { + EntityId originatorId = createEntityId_NULL_UUID(originator); + testSendNotificationMsgToEdgeServiceTimeEntityEqAny(tenantId, actionTypeEdge, cntTimeEdge); + ArgumentMatcher matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, + cntAdditionalInfo); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTimeEdge); + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(HasName entity, HasName originator, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, int cntAdditionalInfo) { + EntityId entityId = createEntityId_NULL_UUID(entity); + EntityId originatorId = createEntityId_NULL_UUID(originator); + testNotificationMsgToEdgeServiceNeverWithActionType(entityId, actionType); + ArgumentMatcher matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, + cntAdditionalInfo); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); + Mockito.reset(tbClusterService, auditLogService); + } + + protected void testNotifyEntityBroadcastEntityStateChangeEventOneTime(HasName entity, EntityId entityId, EntityId originatorId, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, Object... additionalInfo) { + int cntTime = 1; + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); + testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime); Mockito.reset(tbClusterService, auditLogService); } protected void testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId, TenantId tenantId, CustomerId customerId, UserId userId, String userName, ActionType actionType, Object... additionalInfo) { - testNotificationMsgToEdgeServiceNever(entityId); - testLogEntityActionOneTime(entity, originatorId, tenantId, customerId, userId, userName, actionType, additionalInfo); - testPushMsgToRuleEngineOneTime(originatorId, tenantId); - testBroadcastEntityStateChangeEventOneTime(entityId, tenantId); + int cntTime = 1; + testNotificationMsgToEdgeServiceNeverWithActionType(entityId, actionType); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime); + testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime); Mockito.reset(tbClusterService, auditLogService); } - protected void testNotifyEntityError(HasName entity, TenantId tenantId, - UserId userId, String userName, ActionType actionType, Exception exp, - Object... additionalInfo) { - CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID); - EntityId entity_NULL_UUID = EntityIdFactory.getByTypeAndUuid(EntityType.valueOf(entity.getClass().toString() - .substring(entity.getClass().toString().lastIndexOf(".") + 1).toUpperCase(Locale.ENGLISH)), - ModelConstants.NULL_UUID); - testNotificationMsgToEdgeServiceNever(entity_NULL_UUID); - if (additionalInfo.length > 0) { - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), - Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName), - Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType), - Mockito.argThat(argument -> - argument.getMessage().equals(exp.getMessage())), Mockito.eq(additionalInfo)); - } else { - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), - Mockito.eq(customer_NULL_UUID), Mockito.eq(userId), Mockito.eq(userName), - Mockito.eq(entity_NULL_UUID), Mockito.any(entity.getClass()), Mockito.eq(actionType), - Mockito.argThat(argument -> - argument.getMessage().equals(exp.getMessage()))); - } - testPushMsgToRuleEngineNever(entity_NULL_UUID); + protected void testNotifyEntityBroadcastEntityStateChangeEventMany(HasName entity, HasName originator, + TenantId tenantId, CustomerId customerId, + UserId userId, String userName, ActionType actionType, + ActionType actionTypeEdge, + int cntTime, int cntTimeEdge, int cntTimeRuleEngine, + int cntAdditionalInfo) { + EntityId entityId = createEntityId_NULL_UUID(entity); + EntityId originatorId = createEntityId_NULL_UUID(originator); + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionTypeEdge, cntTimeEdge); + ArgumentMatcher matcherEntityClassEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherOriginatorId = argument -> argument.getClass().equals(originatorId.getClass()); + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfoAny(matcherEntityClassEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, actionType, cntTime, + cntAdditionalInfo); + testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTimeRuleEngine); + testBroadcastEntityStateChangeEventTime(entityId, tenantId, cntTime); + } + + protected void testNotifyEntityMsgToEdgePushMsgToCoreOneTime(HasName entity, EntityId entityId, EntityId originatorId, + TenantId tenantId, CustomerId customerId, UserId userId, String userName, + ActionType actionType, Object... additionalInfo) { + int cntTime = 1; + testNotificationMsgToEdgeServiceTime(entityId, tenantId, actionType, cntTime); + testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo); + tesPushMsgToCoreTime(cntTime); Mockito.reset(tbClusterService, auditLogService); } + protected void testNotifyEntityEqualsOneTimeServiceNeverError(HasName entity, TenantId tenantId, + UserId userId, String userName, ActionType actionType, Exception exp, + Object... additionalInfo) { + CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID); + EntityId entity_originator_NULL_UUID = createEntityId_NULL_UUID(entity); + testNotificationMsgToEdgeServiceNeverWithActionType(entity_originator_NULL_UUID, actionType); + ArgumentMatcher matcherEntityEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherError = argument -> argument.getMessage().contains(exp.getMessage()) + & argument.getClass().equals(exp.getClass()); + testLogEntityActionErrorAdditionalInfo(matcherEntityEquals, entity_originator_NULL_UUID, tenantId, customer_NULL_UUID, userId, + userName, actionType, 1, matcherError, extractMatcherAdditionalInfo(additionalInfo)); + testPushMsgToRuleEngineNever(entity_originator_NULL_UUID); + } + + protected void testNotifyEntityIsNullOneTimeEdgeServiceNeverError(HasName entity, TenantId tenantId, + UserId userId, String userName, ActionType actionType, Exception exp, + Object... additionalInfo) { + CustomerId customer_NULL_UUID = (CustomerId) EntityIdFactory.getByTypeAndUuid(EntityType.CUSTOMER, ModelConstants.NULL_UUID); + EntityId entity_originator_NULL_UUID = createEntityId_NULL_UUID(entity); + testNotificationMsgToEdgeServiceNeverWithActionType(entity_originator_NULL_UUID, actionType); + ArgumentMatcher matcherEntityIsNull = Objects::isNull; + ArgumentMatcher matcherError = argument -> argument.getMessage().contains(exp.getMessage()) & + argument.getClass().equals(exp.getClass()); + testLogEntityActionErrorAdditionalInfo(matcherEntityIsNull, entity_originator_NULL_UUID, tenantId, customer_NULL_UUID, + userId, userName, actionType, 1, matcherError, extractMatcherAdditionalInfo(additionalInfo)); + testPushMsgToRuleEngineNever(entity_originator_NULL_UUID); + } + protected void testNotifyEntityNever(EntityId entityId, HasName entity) { + entityId = entityId == null ? createEntityId_NULL_UUID(entity) : entityId; testNotificationMsgToEdgeServiceNever(entityId); testLogEntityActionNever(entityId, entity); testPushMsgToRuleEngineNever(entityId); Mockito.reset(tbClusterService, auditLogService); } + private void testNotificationMsgToEdgeServiceNeverWithActionType(EntityId entityId, ActionType actionType) { + EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ? + EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType); + Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdge(Mockito.any(), + Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.eq(edgeEventActionType)); + } + private void testNotificationMsgToEdgeServiceNever(EntityId entityId) { - Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdgeService(Mockito.any(), + Mockito.verify(tbClusterService, never()).sendNotificationMsgToEdge(Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any(), Mockito.any()); } private void testLogEntityActionNever(EntityId entityId, HasName entity) { + ArgumentMatcher matcherEntity = entity == null ? Objects::isNull : + argument -> argument.getClass().equals(entity.getClass()); Mockito.verify(auditLogService, never()).logEntityAction(Mockito.any(), Mockito.any(), - Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.any(entity.getClass()), + Mockito.any(), Mockito.any(), Mockito.any(entityId.getClass()), Mockito.argThat(matcherEntity), Mockito.any(), Mockito.any()); } @@ -139,44 +335,293 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest { Mockito.any(entityId.getClass()), Mockito.any(), Mockito.any()); } - private void testLogEntityActionOneTime(HasName entity, EntityId originatorId, TenantId tenantId, CustomerId customerId, - UserId userId, String userName, ActionType actionType, Object... additionalInfo) { - if (additionalInfo.length == 0) { - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.eq(customerId), - Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId), - Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull()); - } else { - String additionalInfoStr = extractParameter(String.class, 0, additionalInfo); - Mockito.verify(auditLogService, times(1)).logEntityAction(Mockito.eq(tenantId), Mockito.eq(customerId), - Mockito.eq(userId), Mockito.eq(userName), Mockito.eq(originatorId), - Mockito.eq(entity), Mockito.eq(actionType), Mockito.isNull(), Mockito.eq(additionalInfoStr)); - } + protected void testBroadcastEntityStateChangeEventNever(EntityId entityId) { + Mockito.verify(tbClusterService, never()).broadcastEntityStateChangeEvent(Mockito.any(), + Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class)); + } + + private void testPushMsgToRuleEngineTime(ArgumentMatcher matcherOriginatorId, TenantId tenantId, HasName entity, int cntTime) { + tenantId = tenantId.isNullUid() && ((HasTenantId) entity).getTenantId() != null ? ((HasTenantId) entity).getTenantId() : tenantId; + Mockito.verify(tbClusterService, times(cntTime)).pushMsgToRuleEngine(Mockito.eq(tenantId), + Mockito.argThat(matcherOriginatorId), Mockito.any(TbMsg.class), Mockito.isNull()); } - private void testPushMsgToRuleEngineOneTime(EntityId originatorId, TenantId tenantId) { - Mockito.verify(tbClusterService, times(1)).pushMsgToRuleEngine(Mockito.eq(tenantId), - Mockito.eq(originatorId), Mockito.any(TbMsg.class), Mockito.isNull()); + private void testNotificationMsgToEdgeServiceTime(EntityId entityId, TenantId tenantId, ActionType actionType, int cntTime) { + EdgeEventActionType edgeEventActionType = ActionType.CREDENTIALS_UPDATED.equals(actionType) ? + EdgeEventActionType.CREDENTIALS_UPDATED : edgeTypeByActionType(actionType); + ArgumentMatcher matcherEntityId = cntTime == 1 ? argument -> argument.equals(entityId) : + argument -> argument.getClass().equals(entityId.getClass()); + Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), + Mockito.any(), Mockito.argThat(matcherEntityId), Mockito.any(), Mockito.isNull(), + Mockito.eq(edgeEventActionType)); } - private void testSendNotificationMsgToEdgeServiceOneTime(EntityId entityId, TenantId tenantId, ActionType actionType) { - Mockito.verify(tbClusterService, times(1)).sendNotificationMsgToEdgeService(Mockito.eq(tenantId), - Mockito.isNull(), Mockito.eq(entityId), Mockito.isNull(), Mockito.isNull(), + private void testSendNotificationMsgToEdgeServiceTimeEntityEqAny(TenantId tenantId, ActionType actionType, int cntTime) { + Mockito.verify(tbClusterService, times(cntTime)).sendNotificationMsgToEdge(Mockito.eq(tenantId), + Mockito.any(), Mockito.any(EntityId.class), Mockito.any(), Mockito.isNull(), Mockito.eq(edgeTypeByActionType(actionType))); } - private void testBroadcastEntityStateChangeEventOneTime(EntityId entityId, TenantId tenantId) { - Mockito.verify(tbClusterService, times(1)).broadcastEntityStateChangeEvent(Mockito.eq(tenantId), + protected void testBroadcastEntityStateChangeEventTime(EntityId entityId, TenantId tenantId, int cntTime) { + ArgumentMatcher matcherTenantIdId = cntTime > 1 || tenantId == null ? argument -> argument.getClass().equals(TenantId.class) : + argument -> argument.equals(tenantId) ; + Mockito.verify(tbClusterService, times(cntTime)).broadcastEntityStateChangeEvent(Mockito.argThat(matcherTenantIdId), Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class)); } - private T extractParameter(Class clazz, int index, Object... additionalInfo) { + private void tesPushMsgToCoreTime(int cntTime) { + Mockito.verify(tbClusterService, times(cntTime)).pushMsgToCore(Mockito.any(ToDeviceActorNotificationMsg.class), Mockito.isNull()); + } + + private void testLogEntityAction(HasName entity, EntityId originatorId, TenantId tenantId, + CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, Object... additionalInfo) { + ArgumentMatcher matcherEntityEquals = entity == null ? Objects::isNull : argument -> argument.toString().equals(entity.toString()); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfo(matcherEntityEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, + actionType, cntTime, extractMatcherAdditionalInfo(additionalInfo)); + } + + private void testLogEntityActionEntityEqClass(HasName entity, EntityId originatorId, TenantId tenantId, + CustomerId customerId, UserId userId, String userName, + ActionType actionType, int cntTime, Object... additionalInfo) { + ArgumentMatcher matcherEntityEquals = argument -> argument.getClass().equals(entity.getClass()); + ArgumentMatcher matcherOriginatorId = argument -> argument.equals(originatorId); + ArgumentMatcher matcherCustomerId = customerId == null ? + argument -> argument.getClass().equals(CustomerId.class) : argument -> argument.equals(customerId); + ArgumentMatcher matcherUserId = userId == null ? + argument -> argument.getClass().equals(UserId.class) : argument -> argument.equals(userId); + testLogEntityActionAdditionalInfo(matcherEntityEquals, matcherOriginatorId, tenantId, matcherCustomerId, matcherUserId, userName, + actionType, cntTime, extractMatcherAdditionalInfo(additionalInfo)); + } + + private void testLogEntityActionAdditionalInfo(ArgumentMatcher matcherEntity, ArgumentMatcher matcherOriginatorId, + TenantId tenantId, ArgumentMatcher matcherCustomerId, + ArgumentMatcher matcherUserId, String userName, ActionType actionType, + int cntTime, List> matcherAdditionalInfos) { + switch (matcherAdditionalInfos.size()) { + case 1: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.argThat(matcherAdditionalInfos.get(0))); + break; + case 2: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.argThat(matcherAdditionalInfos.get(0)), + Mockito.argThat(matcherAdditionalInfos.get(1))); + break; + case 3: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.argThat(matcherAdditionalInfos.get(0)), + Mockito.argThat(matcherAdditionalInfos.get(1)), + Mockito.argThat(matcherAdditionalInfos.get(2))); + break; + default: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull()); + } + } + + private void testLogEntityActionAdditionalInfoAny(ArgumentMatcher matcherEntity, ArgumentMatcher matcherOriginatorId, + TenantId tenantId, ArgumentMatcher matcherCustomerId, + ArgumentMatcher matcherUserId, String userName, + ActionType actionType, int cntTime, int cntAdditionalInfo) { + switch (cntAdditionalInfo) { + case 1: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.any()); + break; + case 2: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.any(), + Mockito.any()); + break; + case 3: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull(), + Mockito.any(), + Mockito.any(), + Mockito.any()); + break; + default: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.argThat(matcherCustomerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.argThat(matcherOriginatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.isNull()); + } + } + + private void testLogEntityActionErrorAdditionalInfo(ArgumentMatcher matcherEntity, EntityId originatorId, TenantId tenantId, + CustomerId customerId, UserId userId, String userName, ActionType actionType, + int cntTime, ArgumentMatcher matcherError, + List> matcherAdditionalInfos) { + ArgumentMatcher matcherUserId = userId == null ? argument -> argument.getClass().equals(UserId.class) : + argument -> argument.equals(userId); + switch (matcherAdditionalInfos.size()) { + case 1: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError), + Mockito.argThat(matcherAdditionalInfos.get(0))); + break; + case 2: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError), + Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(0))), + Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(1)))); + case 3: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError), + Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(0))), + Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(1))), + Mockito.argThat(Mockito.eq(matcherAdditionalInfos.get(2)))); + break; + default: + Mockito.verify(auditLogService, times(cntTime)) + .logEntityAction(Mockito.eq(tenantId), + Mockito.eq(customerId), + Mockito.argThat(matcherUserId), + Mockito.eq(userName), + Mockito.eq(originatorId), + Mockito.argThat(matcherEntity), + Mockito.eq(actionType), + Mockito.argThat(matcherError)); + } + } + + private List> extractMatcherAdditionalInfo(Object... additionalInfos) { + List> matcherAdditionalInfos = new ArrayList<>(additionalInfos.length); + for (Object additionalInfo : additionalInfos) { + matcherAdditionalInfos.add(argument -> argument.equals(extractParameter(additionalInfo.getClass(), additionalInfo))); + } + return matcherAdditionalInfos; + } + + private List> extractMatcherAdditionalInfoClass(Object... additionalInfos) { + List> matcherAdditionalInfos = new ArrayList<>(additionalInfos.length); + for (Object additionalInfo : additionalInfos) { + matcherAdditionalInfos.add(argument -> argument.getClass().equals(extractParameter(additionalInfo.getClass(), additionalInfo).getClass())); + } + return matcherAdditionalInfos; + } + + private T extractParameter(Class clazz, Object additionalInfo) { T result = null; - if (additionalInfo != null && additionalInfo.length > index) { - Object paramObject = additionalInfo[index]; + if (additionalInfo != null) { + Object paramObject = additionalInfo; if (clazz.isInstance(paramObject)) { result = clazz.cast(paramObject); } } return result; } + + protected EntityId createEntityId_NULL_UUID(HasName entity) { + return EntityIdFactory.getByTypeAndUuid(entityClassToEntityTypeName(entity), ModelConstants.NULL_UUID); + } + + protected String msgErrorFieldLength(String fieldName) { + return "length of " + fieldName + " must be equal or less than 255"; + } + + protected String msgErrorNoFound(String entityClassName, String assetIdStr) { + return entityClassName + " with id [" + assetIdStr + "] is not found"; + } + + private String entityClassToEntityTypeName(HasName entity) { + String entityType = entityClassToString(entity); + return "SAVE_OTA_PACKAGE_INFO_REQUEST".equals(entityType) || "OTA_PACKAGE_INFO".equals(entityType)? + EntityType.OTA_PACKAGE.name().toUpperCase(Locale.ENGLISH) : entityType; + } + + private String entityClassToString(HasName entity) { + String className = entity.getClass().toString() + .substring(entity.getClass().toString().lastIndexOf(".") + 1); + List str = className.chars() + .mapToObj(x -> (Character.isUpperCase(x)) ? "_" + Character.toString(x) : Character.toString(x)) + .collect(Collectors.toList()); + return String.join("", str).toUpperCase(Locale.ENGLISH).substring(1); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 24ee2ba371..75d3a0aa97 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -69,6 +69,7 @@ import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeCon 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.QueueId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; @@ -237,6 +238,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { loginSysAdmin(); doDelete("/api/tenant/" + tenantId.getId().toString()) .andExpect(status().isOk()); + deleteDifferentTenant(); verifyNoTenantsLeft(); @@ -277,12 +279,13 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { login(userName, password); } - private Tenant savedDifferentTenant; + protected Tenant savedDifferentTenant; + protected User savedDifferentTenantUser; private Customer savedDifferentCustomer; protected void loginDifferentTenant() throws Exception { if (savedDifferentTenant != null) { - login(savedDifferentTenant.getEmail(), TENANT_ADMIN_PASSWORD); + login(DIFFERENT_TENANT_ADMIN_EMAIL, DIFFERENT_TENANT_ADMIN_PASSWORD); } else { loginSysAdmin(); @@ -295,8 +298,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN); differentTenantAdmin.setTenantId(savedDifferentTenant.getId()); differentTenantAdmin.setEmail(DIFFERENT_TENANT_ADMIN_EMAIL); - - createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD); + savedDifferentTenantUser = createUserAndLogin(differentTenantAdmin, DIFFERENT_TENANT_ADMIN_PASSWORD); } } @@ -334,6 +336,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { loginSysAdmin(); doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) .andExpect(status().isOk()); + savedDifferentTenant = null; } } @@ -656,7 +659,6 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected T readResponse(ResultActions result, TypeReference type) throws Exception { byte[] content = result.andReturn().getResponse().getContentAsByteArray(); - ObjectMapper mapper = new ObjectMapper(); return mapper.readerFor(type).readValue(content); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java index b89133f29a..81ecce8300 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java @@ -18,14 +18,29 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.service.mail.DefaultMailService; -import static org.hamcrest.Matchers.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseAdminControllerTest extends AbstractControllerTest { + @Autowired + MailService mailService; + + @Autowired + DefaultMailService defaultMailService; + @Test public void testFindAdminSettingsByKey() throws Exception { loginSysAdmin(); @@ -100,5 +115,28 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest { doPost("/api/admin/settings/testMail", adminSettings) .andExpect(status().isOk()); } - + + @Test + public void testSendTestMailTimeout() throws Exception { + loginSysAdmin(); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + ObjectNode objectNode = JacksonUtil.fromString(adminSettings.getJsonValue().toString(), ObjectNode.class); + + objectNode.put("smtpHost", "mail.gandi.net"); + objectNode.put("timeout", 1_000); + objectNode.put("username", "username"); + objectNode.put("password", "password"); + + adminSettings.setJsonValue(objectNode); + + Mockito.doAnswer((invocations) -> { + var jsonConfig = (JsonNode) invocations.getArgument(0); + var email = (String) invocations.getArgument(1); + + defaultMailService.sendTestMail(jsonConfig, email); + return null; + }).when(mailService).sendTestMail(Mockito.any(), Mockito.anyString()); + doPost("/api/admin/settings/testMail", adminSettings).andExpect(status().is5xxServerError()); + Mockito.doNothing().when(mailService).sendTestMail(Mockito.any(), Mockito.any()); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index 7f1943d8bd..14e260ab14 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.PageData; import java.util.LinkedList; import java.util.List; +import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j @@ -132,7 +133,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doPost("/api/alarm", alarm).andExpect(status().isForbidden()); + doPost("/api/alarm", alarm) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); testNotifyEntityNever(alarm.getId(), alarm); } @@ -147,7 +150,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doPost("/api/alarm", alarm).andExpect(status().isForbidden()); + doPost("/api/alarm", alarm) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); testNotifyEntityNever(alarm.getId(), alarm); } @@ -175,7 +180,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk()); testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(), - tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED); + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED); } @Test @@ -187,7 +192,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden()); + doDelete("/api/alarm/" + alarm.getId()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); testNotifyEntityNever(alarm.getId(), alarm); } @@ -201,7 +208,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden()); + doDelete("/api/alarm/" + alarm.getId()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); testNotifyEntityNever(alarm.getId(), alarm); } @@ -265,7 +274,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden()); + doPost("/api/alarm/" + alarm.getId() + "/clear") + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); testNotifyEntityNever(alarm.getId(), alarm); } @@ -279,7 +290,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden()); + doPost("/api/alarm/" + alarm.getId() + "/clear") + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); testNotifyEntityNever(alarm.getId(), alarm); } @@ -293,7 +306,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden()); + doPost("/api/alarm/" + alarm.getId() + "/ack") + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); testNotifyEntityNever(alarm.getId(), alarm); } @@ -307,7 +322,9 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { Mockito.reset(tbClusterService, auditLogService); - doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden()); + doPost("/api/alarm/" + alarm.getId() + "/ack") + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); } @Test @@ -355,7 +372,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { loginDifferentCustomer(); doGet("/api/alarm/" + EntityType.DEVICE + "/" + customerDevice.getUuidId() + "?page=0&pageSize=" + size) - .andExpect(status().isForbidden()); + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); } @Test diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java index 0861ab34c8..0aac87cea0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java @@ -22,17 +22,20 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.stats.DefaultRuleEngineStatisticsService; @@ -83,8 +86,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Asset asset = new Asset(); asset.setName("My asset"); asset.setType("default"); + + Mockito.reset(tbClusterService, auditLogService); + Asset savedAsset = doPost("/api/asset", asset, Asset.class); + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); + Assert.assertNotNull(savedAsset); Assert.assertNotNull(savedAsset.getId()); Assert.assertTrue(savedAsset.getCreatedTime() > 0); @@ -93,9 +102,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId()); Assert.assertEquals(asset.getName(), savedAsset.getName()); + Mockito.reset(tbClusterService, auditLogService); + savedAsset.setName("My new asset"); doPost("/api/asset", savedAsset, Asset.class); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); + Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(foundAsset.getName(), savedAsset.getName()); } @@ -105,13 +119,38 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Asset asset = new Asset(); asset.setName(RandomStringUtils.randomAlphabetic(300)); asset.setType("default"); - doPost("/api/asset", asset).andExpect(statusReason(containsString("length of name must be equal or less than 255"))); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = msgErrorFieldLength("name"); + doPost("/api/asset", asset) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + asset.setName("Normal name"); asset.setType(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/asset", asset).andExpect(statusReason(containsString("length of type must be equal or less than 255"))); + msgError = msgErrorFieldLength("type"); + doPost("/api/asset", asset) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + asset.setType("default"); asset.setLabel(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/asset", asset).andExpect(statusReason(containsString("length of label must be equal or less than 255"))); + msgError = msgErrorFieldLength("label"); + doPost("/api/asset", asset) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -122,7 +161,21 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { Asset savedAsset = doPost("/api/asset", asset, Asset.class); loginDifferentTenant(); - doPost("/api/asset", savedAsset, Asset.class, status().isForbidden()); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/asset", savedAsset) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedAsset.getId(), savedAsset); + + doDelete("/api/asset/" + savedAsset.getId().getId().toString()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedAsset.getId(), savedAsset); + deleteDifferentTenant(); } @@ -140,12 +193,21 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @Test public void testFindAssetTypesByTenantId() throws Exception { List assets = new ArrayList<>(); - for (int i = 0; i < 3; i++) { + + Mockito.reset(tbClusterService, auditLogService); + + int cntTime = 3; + for (int i = 0; i < cntTime; i++) { Asset asset = new Asset(); asset.setName("My asset B" + i); asset.setType("typeB"); assets.add(doPost("/api/asset", asset, Asset.class)); } + + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Asset(), new Asset(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntTime); + for (int i = 0; i < 7; i++) { Asset asset = new Asset(); asset.setName("My asset C" + i); @@ -176,11 +238,19 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/asset/" + savedAsset.getId().getId().toString()) .andExpect(status().isOk()); - doGet("/api/asset/" + savedAsset.getId().getId().toString()) - .andExpect(status().isNotFound()); + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, savedAsset.getId().getId().toString()); + + String assetIdStr = savedAsset.getId().getId().toString(); + doGet("/api/asset/" + assetIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Asset", assetIdStr)))); } @Test @@ -202,8 +272,15 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { view.setType("default"); EntityView savedView = doPost("/api/entityView", view, EntityView.class); + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Can't delete asset that has entity views"; doDelete("/api/asset/" + savedAsset1.getId().getId().toString()) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityIsNullOneTimeEdgeServiceNeverError(savedAsset1, savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, new DataValidationException(msgError), savedAsset1.getId().getId().toString()); savedView.setEntityId(savedAsset2.getId()); @@ -212,26 +289,42 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { doDelete("/api/asset/" + savedAsset1.getId().getId().toString()) .andExpect(status().isOk()); - doGet("/api/asset/" + savedAsset1.getId().getId().toString()) - .andExpect(status().isNotFound()); + String assetIdStr = savedAsset1.getId().getId().toString(); + doGet("/api/asset/" + assetIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Asset", assetIdStr)))); } @Test public void testSaveAssetWithEmptyType() throws Exception { Asset asset = new Asset(); asset.setName("My asset"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Asset type " + msgErrorShouldBeSpecified; doPost("/api/asset", asset) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Asset type should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testSaveAssetWithEmptyName() throws Exception { Asset asset = new Asset(); asset.setType("default"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Asset name " + msgErrorShouldBeSpecified; doPost("/api/asset", asset) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Asset name should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(asset, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -245,17 +338,29 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { customer.setTitle("My customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + Mockito.reset(tbClusterService, auditLogService); + Asset assignedAsset = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(savedCustomer.getId(), assignedAsset.getCustomerId()); + testNotifyEntityAllOneTime(assignedAsset, assignedAsset.getId(), assignedAsset.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ASSIGNED_TO_CUSTOMER, assignedAsset.getId().toString(), savedCustomer.getId().toString(), savedCustomer.getTitle()); + Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(savedCustomer.getId(), foundAsset.getCustomerId()); + Mockito.reset(tbClusterService, auditLogService); + Asset unassignedAsset = doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedAsset.getCustomerId().getId()); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_CUSTOMER, savedAsset.getId().toString(), savedCustomer.getId().toString(), savedCustomer.getTitle()); + foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId()); } @@ -267,9 +372,15 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); - doPost("/api/customer/" + Uuids.timeBased().toString() + Mockito.reset(tbClusterService, auditLogService); + + String customerIdStr = Uuids.timeBased().toString(); + doPost("/api/customer/" + customerIdStr + "/asset/" + savedAsset.getId().getId().toString()) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr)))); + + testNotifyEntityNever(asset.getId(), asset); } @Test @@ -288,7 +399,7 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { tenantAdmin2.setFirstName("Joe"); tenantAdmin2.setLastName("Downs"); - tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); + createUserAndLogin(tenantAdmin2, "testPassword1"); Customer customer = new Customer(); customer.setTitle("Different customer"); @@ -301,9 +412,14 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString()) - .andExpect(status().isForbidden()); + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedAsset.getId(), savedAsset); loginSysAdmin(); @@ -314,7 +430,11 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { @Test public void testFindTenantAssets() throws Exception { List assets = new ArrayList<>(); - for (int i = 0; i < 178; i++) { + int cntEntity = 178; + + Mockito.reset(tbClusterService, auditLogService); + + for (int i = 0; i < cntEntity; i++) { Asset asset = new Asset(); asset.setName("Asset" + i); asset.setType("default"); @@ -325,13 +445,18 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); loadedAssets.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Asset(), new Asset(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + loadedAssets.removeIf(asset -> asset.getType().equals(DefaultRuleEngineStatisticsService.TB_SERVICE_QUEUE)); Collections.sort(assets, idComparator); @@ -370,7 +495,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); loadedAssetsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -386,7 +512,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); loadedAssetsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -405,7 +532,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -416,7 +544,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/tenant/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -453,7 +582,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>() { + }, pageLink, type1); loadedAssetsType1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -469,7 +599,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4); do { pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>() { + }, pageLink, type2); loadedAssetsType2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -488,7 +619,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>() { + }, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -499,7 +631,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>() { + }, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -526,7 +659,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); loadedAssets.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -578,7 +712,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); loadedAssetsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -594,7 +729,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); loadedAssetsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -613,7 +749,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -624,7 +761,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -670,7 +808,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>() { + }, pageLink, type1); loadedAssetsType1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -686,7 +825,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>() { + }, pageLink, type2); loadedAssetsType2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -705,7 +845,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type1); + new TypeReference>() { + }, pageLink, type1); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -716,7 +857,8 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { pageLink = new PageLink(4); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&", - new TypeReference>(){}, pageLink, type2); + new TypeReference>() { + }, pageLink, type2); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } @@ -731,19 +873,35 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest { asset.setType("default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/edge/" + savedEdge.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_EDGE, + savedAsset.getId().getId().toString(), savedEdge.getId().getId().toString(), edge.getName()); + + PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?", - new TypeReference>() {}, new PageLink(100)); + new TypeReference>() { + }, new PageLink(100)); Assert.assertEquals(1, pageData.getData().size()); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/edge/" + savedEdge.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + + testNotifyEntityAllOneTime(savedAsset, savedAsset.getId(), savedAsset.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_EDGE, savedAsset.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName()); + pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/assets?", - new TypeReference>() {}, new PageLink(100)); + new TypeReference>() { + }, new PageLink(100)); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java index 1f94fa4784..bd0a416ccf 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java @@ -31,6 +31,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -108,7 +109,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest doPost("/api/customer", savedCustomer, Customer.class); testNotifyEntityAllOneTime(savedCustomer, savedCustomer.getId(), savedCustomer.getId(), savedCustomer.getTenantId(), - savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + new CustomerId(CustomerId.NULL_UUID), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); Customer foundCustomer = doGet("/api/customer/" + savedCustomer.getId().getId().toString(), Customer.class); @@ -121,57 +122,73 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest @Test public void testSaveCustomerWithViolationOfValidation() throws Exception { Customer customer = new Customer(); - String validationError = "Validation error: "; customer.setTitle(RandomStringUtils.randomAlphabetic(300)); Mockito.reset(tbClusterService, auditLogService); - String msgError = "length of title must be equal or less than 255"; - doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); + String msgError = msgErrorFieldLength("title"); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + customer.setTenantId(savedTenant.getId()); + testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setTitle("Normal title"); customer.setCity(RandomStringUtils.randomAlphabetic(300)); - msgError = "length of city must be equal or less than 255"; - doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); + msgError = msgErrorFieldLength("city"); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setCity("Normal city"); customer.setCountry(RandomStringUtils.randomAlphabetic(300)); - msgError = "length of country must be equal or less than 255"; - doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); + msgError = msgErrorFieldLength("country"); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setCountry("Ukraine"); customer.setPhone(RandomStringUtils.randomAlphabetic(300)); - msgError = "length of phone must be equal or less than 255"; - doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); + msgError = msgErrorFieldLength("phone"); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setPhone("+3892555554512"); customer.setState(RandomStringUtils.randomAlphabetic(300)); - msgError = "length of state must be equal or less than 255"; - doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); + msgError = msgErrorFieldLength("state"); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); customer.setState("Normal state"); customer.setZip(RandomStringUtils.randomAlphabetic(300)); - msgError = "length of zip or postal code must be equal or less than 255"; - doPost("/api/customer", customer).andExpect(statusReason(containsString(msgError))); - - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(validationError + msgError)); + msgError = msgErrorFieldLength("zip or postal code"); + doPost("/api/customer", customer) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -189,9 +206,17 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest testNotifyEntityNever(savedCustomer.getId(), savedCustomer); + doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedCustomer.getId(), savedCustomer); + deleteDifferentTenant(); login(tenantAdmin.getName(), "testPassword1"); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) .andExpect(status().isOk()); @@ -210,8 +235,6 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Assert.assertNotNull(foundCustomer); Assert.assertEquals(savedCustomer, foundCustomer); - Mockito.reset(tbClusterService, auditLogService); - doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) .andExpect(status().isOk()); } @@ -231,14 +254,16 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest savedCustomer.getId(), savedCustomer.getTenantId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedCustomer.getId().getId().toString()); - doGet("/api/customer/" + savedCustomer.getId().getId().toString()) - .andExpect(status().isNotFound()); + String customerIdStr = savedCustomer.getId().getId().toString(); + doGet("/api/customer/" + customerIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr)))); } @Test public void testSaveCustomerWithEmptyTitle() throws Exception { Customer customer = new Customer(); - String msgError = "Customer title should be specified"; + String msgError = "Customer title " + msgErrorShouldBeSpecified; Mockito.reset(tbClusterService, auditLogService); @@ -246,8 +271,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!")); + testNotifyEntityEqualsOneTimeServiceNeverError(customer,savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -263,16 +288,20 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(msgError))); - testNotifyEntityError(customer, savedTenant.getId(), - tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError + "!")); + testNotifyEntityEqualsOneTimeServiceNeverError(customer, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testFindCustomers() throws Exception { TenantId tenantId = savedTenant.getId(); - List> futures = new ArrayList<>(135); - for (int i = 0; i < 135; i++) { + int cntEntity = 135; + + Mockito.reset(tbClusterService, auditLogService); + + List> futures = new ArrayList<>(cntEntity); + for (int i = 0; i < cntEntity; i++) { Customer customer = new Customer(); customer.setTenantId(tenantId); customer.setTitle("Customer" + i); @@ -281,6 +310,10 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest } List customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Customer(), new Customer(), + tenantId, tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + List loadedCustomers = new ArrayList<>(135); PageLink pageLink = new PageLink(23); PageData pageData = null; @@ -369,5 +402,4 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java index 170b3ec817..66fc1a5178 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java @@ -22,16 +22,19 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; @@ -41,54 +44,66 @@ import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseDashboardControllerTest extends AbstractControllerTest { - + private IdComparator idComparator = new IdComparator<>(); - + private Tenant savedTenant; private User tenantAdmin; - + @Before public void beforeTest() throws Exception { loginSysAdmin(); - + Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); savedTenant = doPost("/api/tenant", tenant, Tenant.class); Assert.assertNotNull(savedTenant); - + tenantAdmin = new User(); tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setTenantId(savedTenant.getId()); tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setFirstName("Joe"); tenantAdmin.setLastName("Downs"); - + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); } - + @After public void afterTest() throws Exception { loginSysAdmin(); - - doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); } - + @Test public void testSaveDashboard() throws Exception { Dashboard dashboard = new Dashboard(); dashboard.setTitle("My dashboard"); + + Mockito.reset(tbClusterService, auditLogService); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED); + Assert.assertNotNull(savedDashboard); Assert.assertNotNull(savedDashboard.getId()); Assert.assertTrue(savedDashboard.getCreatedTime() > 0); Assert.assertEquals(savedTenant.getId(), savedDashboard.getTenantId()); Assert.assertEquals(dashboard.getTitle(), savedDashboard.getTitle()); - + savedDashboard.setTitle("My new dashboard"); + + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/dashboard", savedDashboard, Dashboard.class); - + + testNotifyEntityAllOneTime(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); + Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle()); } @@ -97,7 +112,18 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest public void testSaveDashboardInfoWithViolationOfValidation() throws Exception { Dashboard dashboard = new Dashboard(); dashboard.setTitle(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/dashboard", dashboard).andExpect(statusReason(containsString("length of title must be equal or less than 255"))); + String msgError = msgErrorFieldLength("title"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/dashboard", dashboard) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + dashboard.setTenantId(savedTenant.getId()); + testNotifyEntityEqualsOneTimeServiceNeverError(dashboard, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); } @Test @@ -107,10 +133,16 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); loginDifferentTenant(); + + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/dashboard", savedDashboard, Dashboard.class, status().isForbidden()); + + testNotifyEntityNever(savedDashboard.getId(), savedDashboard); + deleteDifferentTenant(); } - + @Test public void testFindDashboardById() throws Exception { Dashboard dashboard = new Dashboard(); @@ -120,48 +152,74 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest Assert.assertNotNull(foundDashboard); Assert.assertEquals(savedDashboard, foundDashboard); } - + @Test public void testDeleteDashboard() throws Exception { Dashboard dashboard = new Dashboard(); dashboard.setTitle("My dashboard"); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - - doDelete("/api/dashboard/"+savedDashboard.getId().getId().toString()) - .andExpect(status().isOk()); - doGet("/api/dashboard/"+savedDashboard.getId().getId().toString()) - .andExpect(status().isNotFound()); + Mockito.reset(tbClusterService, auditLogService); + + doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString()).andExpect(status().isOk()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), + savedDashboard.getTenantId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, + savedDashboard.getId().getId().toString()); + + String dashboardIdStr = savedDashboard.getId().getId().toString(); + doGet("/api/dashboard/" + savedDashboard.getId().getId().toString()) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Dashboard", dashboardIdStr)))); } - + @Test public void testSaveDashboardWithEmptyTitle() throws Exception { Dashboard dashboard = new Dashboard(); + String msgError = "Dashboard title " + msgErrorShouldBeSpecified;; + + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/dashboard", dashboard) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Dashboard title should be specified"))); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(dashboard, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } - + @Test public void testAssignUnassignDashboardToCustomer() throws Exception { Dashboard dashboard = new Dashboard(); dashboard.setTitle("My dashboard"); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - + Customer customer = new Customer(); customer.setTitle("My customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - - Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + + Mockito.reset(tbClusterService, auditLogService); + + Dashboard assignedDashboard = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Assert.assertTrue(assignedDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo())); + testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, + assignedDashboard .getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); + Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Assert.assertTrue(foundDashboard.getAssignedCustomers().contains(savedCustomer.toShortCustomerInfo())); - Dashboard unassignedDashboard = - doDelete("/api/customer/"+savedCustomer.getId().getId().toString()+"/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + Mockito.reset(tbClusterService, auditLogService); + + Dashboard unassignedDashboard = + doDelete("/api/customer/" + savedCustomer.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + + testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedDashboard, assignedDashboard.getId(), assignedDashboard.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, + unassignedDashboard.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); Assert.assertTrue(unassignedDashboard.getAssignedCustomers() == null || unassignedDashboard.getAssignedCustomers().isEmpty()); @@ -169,22 +227,27 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest Assert.assertTrue(foundDashboard.getAssignedCustomers() == null || foundDashboard.getAssignedCustomers().isEmpty()); } - + @Test public void testAssignDashboardToNonExistentCustomer() throws Exception { Dashboard dashboard = new Dashboard(); dashboard.setTitle("My dashboard"); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - - doPost("/api/customer/" + Uuids.timeBased().toString() + + String customerIdStr = Uuids.timeBased().toString(); + doPost("/api/customer/" + customerIdStr + "/dashboard/" + savedDashboard.getId().getId().toString()) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr)))); + + Mockito.reset(tbClusterService, auditLogService); + testNotifyEntityNever(savedDashboard.getId(), savedDashboard); } - + @Test public void testAssignDashboardToCustomerFromDifferentTenant() throws Exception { loginSysAdmin(); - + Tenant tenant2 = new Tenant(); tenant2.setTitle("Different tenant"); Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class); @@ -196,100 +259,124 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest tenantAdmin2.setEmail("tenant3@thingsboard.org"); tenantAdmin2.setFirstName("Joe"); tenantAdmin2.setLastName("Downs"); - - tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); - + + createUserAndLogin(tenantAdmin2, "testPassword1"); + Customer customer = new Customer(); customer.setTitle("Different customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); login(tenantAdmin.getEmail(), "testPassword1"); - + Dashboard dashboard = new Dashboard(); dashboard.setTitle("My dashboard"); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString()) - .andExpect(status().isForbidden()); - + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + Mockito.reset(tbClusterService, auditLogService); + testNotifyEntityNever(savedDashboard.getId(), savedDashboard); + + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedDashboard.getId(), savedDashboard); + loginSysAdmin(); - - doDelete("/api/tenant/"+savedTenant2.getId().getId().toString()) - .andExpect(status().isOk()); + + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) + .andExpect(status().isOk()); } @Test public void testFindTenantDashboards() throws Exception { List dashboards = new ArrayList<>(); - for (int i=0;i<173;i++) { + + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = 173; + for (int i = 0; i < cntEntity; i++) { Dashboard dashboard = new Dashboard(); - dashboard.setTitle("Dashboard"+i); + dashboard.setTitle("Dashboard" + i); dashboards.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class))); } + + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Dashboard(), new Dashboard(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + List loadedDashboards = new ArrayList<>(); PageLink pageLink = new PageLink(24); PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>() { + }, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(dashboards, idComparator); Collections.sort(loadedDashboards, idComparator); - + Assert.assertEquals(dashboards, loadedDashboards); } - + @Test public void testFindTenantDashboardsByTitle() throws Exception { String title1 = "Dashboard title 1"; List dashboardsTitle1 = new ArrayList<>(); - for (int i=0;i<134;i++) { + int cntEntity = 134; + for (int i = 0; i < cntEntity; i++) { Dashboard dashboard = new Dashboard(); - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); - String title = title1+suffix; + String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); dashboard.setTitle(title); dashboardsTitle1.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class))); } String title2 = "Dashboard title 2"; List dashboardsTitle2 = new ArrayList<>(); - for (int i=0;i<112;i++) { + + for (int i = 0; i < 112; i++) { Dashboard dashboard = new Dashboard(); - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); - String title = title2+suffix; + String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); dashboard.setTitle(title); dashboardsTitle2.add(new DashboardInfo(doPost("/api/dashboard", dashboard, Dashboard.class))); } - + List loadedDashboardsTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; do { - pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>() { + }, pageLink); loadedDashboardsTitle1.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(dashboardsTitle1, idComparator); Collections.sort(loadedDashboardsTitle1, idComparator); - + Assert.assertEquals(dashboardsTitle1, loadedDashboardsTitle1); - + List loadedDashboardsTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { - pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>() { + }, pageLink); loadedDashboardsTitle2.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -298,63 +385,79 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest Collections.sort(dashboardsTitle2, idComparator); Collections.sort(loadedDashboardsTitle2, idComparator); - + Assert.assertEquals(dashboardsTitle2, loadedDashboardsTitle2); - + + Mockito.reset(tbClusterService, auditLogService); + for (DashboardInfo dashboard : loadedDashboardsTitle1) { - doDelete("/api/dashboard/"+dashboard.getId().getId().toString()) - .andExpect(status().isOk()); + doDelete("/api/dashboard/" + dashboard.getId().getId().toString()) + .andExpect(status().isOk()); } - + + testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Dashboard(), new Dashboard(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, cntEntity, 1); + pageLink = new PageLink(4, 0, title1); - pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - + for (DashboardInfo dashboard : loadedDashboardsTitle2) { - doDelete("/api/dashboard/"+dashboard.getId().getId().toString()) - .andExpect(status().isOk()); + doDelete("/api/dashboard/" + dashboard.getId().getId().toString()) + .andExpect(status().isOk()); } - + pageLink = new PageLink(4, 0, title2); - pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", - new TypeReference>(){}, pageLink); + pageData = doGetTypedWithPageLink("/api/tenant/dashboards?", + new TypeReference>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } - + @Test public void testFindCustomerDashboards() throws Exception { Customer customer = new Customer(); customer.setTitle("Test customer"); customer = doPost("/api/customer", customer, Customer.class); CustomerId customerId = customer.getId(); - + + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = 173; List dashboards = new ArrayList<>(); - for (int i=0;i<173;i++) { + for (int i = 0; i < cntEntity; i++) { Dashboard dashboard = new Dashboard(); - dashboard.setTitle("Dashboard"+i); + dashboard.setTitle("Dashboard" + i); dashboard = doPost("/api/dashboard", dashboard, Dashboard.class); dashboards.add(new DashboardInfo(doPost("/api/customer/" + customerId.getId().toString() - + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class))); + + "/dashboard/" + dashboard.getId().getId().toString(), Dashboard.class))); } - + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Dashboard(), new Dashboard(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity*2); + List loadedDashboards = new ArrayList<>(); PageLink pageLink = new PageLink(21); PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/dashboards?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); loadedDashboards.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(dashboards, idComparator); Collections.sort(loadedDashboards, idComparator); - + Assert.assertEquals(dashboards, loadedDashboards); } @@ -367,11 +470,18 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest dashboard.setTitle("My dashboard"); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/edge/" + savedEdge.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + testNotifyEntityAllOneTime(savedDashboard, savedDashboard.getId(), savedDashboard.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_EDGE, + savedDashboard.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName()); + PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/dashboards?", - new TypeReference>() {}, new PageLink(100)); + new TypeReference>() { + }, new PageLink(100)); Assert.assertEquals(1, pageData.getData().size()); @@ -379,7 +489,8 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/dashboards?", - new TypeReference>() {}, new PageLink(100)); + new TypeReference>() { + }, new PageLink(100)); Assert.assertEquals(0, pageData.getData().size()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java index d6e30c644c..ee4d005233 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java @@ -21,22 +21,28 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; -import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -44,7 +50,10 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; import java.util.ArrayList; import java.util.List; @@ -52,13 +61,15 @@ import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; -@Slf4j public abstract class BaseDeviceControllerTest extends AbstractControllerTest { - static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() { - }; + static final TypeReference> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() {}; ListeningExecutorService executor; @@ -68,9 +79,11 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { private Tenant savedTenant; private User tenantAdmin; + @SpyBean + private GatewayNotificationsService gatewayNotificationsService; + @Before public void beforeTest() throws Exception { - log.debug("beforeTest"); executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); loginSysAdmin(); @@ -92,14 +105,12 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @After public void afterTest() throws Exception { - log.debug("afterTest..."); executor.shutdownNow(); loginSysAdmin(); doDelete("/api/tenant/" + savedTenant.getId().getId()) .andExpect(status().isOk()); - log.debug("afterTest done"); } @Test @@ -107,8 +118,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Device device = new Device(); device.setName("My device"); device.setType("default"); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + Device savedDevice = doPost("/api/device", device, Device.class); + Device oldDevice = new Device(savedDevice); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + testNotificationUpdateGatewayNever(); + Assert.assertNotNull(savedDevice); Assert.assertNotNull(savedDevice.getId()); Assert.assertTrue(savedDevice.getCreatedTime() > 0); @@ -127,9 +148,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertNotNull(deviceCredentials.getCredentialsId()); Assert.assertEquals(20, deviceCredentials.getCredentialsId().length()); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + savedDevice.setName("My new device"); doPost("/api/device", savedDevice, Device.class); + testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED); + testNotificationUpdateGatewayOneTime(savedDevice, oldDevice); + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); } @@ -139,13 +166,41 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Device device = new Device(); device.setName(RandomStringUtils.randomAlphabetic(300)); device.setType("default"); - doPost("/api/device", device).andExpect(statusReason(containsString("length of name must be equal or less than 255"))); - device.setName("Normal Name"); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + String msgError = msgErrorFieldLength("name"); + doPost("/api/device", device) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(device, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotificationUpdateGatewayNever(); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + device.setTenantId(savedTenant.getId()); + msgError = msgErrorFieldLength("type"); device.setType(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/device", device).andExpect(statusReason(containsString("length of type must be equal or less than 255"))); + doPost("/api/device", device) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(device, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotificationUpdateGatewayNever(); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + msgError = msgErrorFieldLength("label"); device.setType("Normal type"); device.setLabel(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/device", device).andExpect(statusReason(containsString("length of label must be equal or less than 255"))); + doPost("/api/device", device) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(device, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotificationUpdateGatewayNever(); } @Test @@ -155,10 +210,89 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); loginDifferentTenant(); - doPost("/api/device", savedDevice, Device.class, status().isNotFound()); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + String savedDeviceIdStr = savedDevice.getId().getId().toString(); + doPost("/api/device", savedDevice) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", savedDeviceIdStr)))); + + testNotifyEntityNever(savedDevice.getId(), savedDevice); + testNotificationUpdateGatewayNever(); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + doDelete("/api/device/" + savedDeviceIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", savedDeviceIdStr)))); + + testNotifyEntityNever(savedDevice.getId(), savedDevice); + testNotificationUpdateGatewayNever(); + deleteDifferentTenant(); } + @Test + public void testSaveDeviceWithProfileFromDifferentTenant() throws Exception { + loginDifferentTenant(); + DeviceProfile differentProfile = createDeviceProfile("Different profile"); + differentProfile = doPost("/api/deviceProfile", differentProfile, DeviceProfile.class); + + loginTenantAdmin(); + Device device = new Device(); + device.setName("My device"); + device.setDeviceProfileId(differentProfile.getId()); + doPost("/api/device", device).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Device can`t be referencing to device profile from different tenant!"))); + } + + @Test + public void testSaveDeviceWithFirmwareFromDifferentTenant() throws Exception { + loginDifferentTenant(); + DeviceProfile differentProfile = createDeviceProfile("Different profile"); + differentProfile = doPost("/api/deviceProfile", differentProfile, DeviceProfile.class); + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); + firmwareInfo.setDeviceProfileId(differentProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("title"); + firmwareInfo.setVersion("1.0"); + firmwareInfo.setUrl("test.url"); + firmwareInfo.setUsesUrl(true); + OtaPackageInfo savedFw = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + + loginTenantAdmin(); + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + device.setFirmwareId(savedFw.getId()); + doPost("/api/device", device).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Can't assign firmware from different tenant!"))); + } + + @Test + public void testSaveDeviceWithSoftwareFromDifferentTenant() throws Exception { + loginDifferentTenant(); + DeviceProfile differentProfile = createDeviceProfile("Different profile"); + differentProfile = doPost("/api/deviceProfile", differentProfile, DeviceProfile.class); + SaveOtaPackageInfoRequest softwareInfo = new SaveOtaPackageInfoRequest(); + softwareInfo.setDeviceProfileId(differentProfile.getId()); + softwareInfo.setType(SOFTWARE); + softwareInfo.setTitle("title"); + softwareInfo.setVersion("1.0"); + softwareInfo.setUrl("test.url"); + softwareInfo.setUsesUrl(true); + OtaPackageInfo savedSw = doPost("/api/otaPackage", softwareInfo, OtaPackageInfo.class); + + loginTenantAdmin(); + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + device.setSoftwareId(savedSw.getId()); + doPost("/api/device", device).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Can't assign software from different tenant!"))); + } + @Test public void testFindDeviceById() throws Exception { Device device = new Device(); @@ -173,12 +307,23 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @Test public void testFindDeviceTypesByTenantId() throws Exception { List devices = new ArrayList<>(); - for (int i = 0; i < 3; i++) { + + int cntEntity = 3; + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + for (int i = 0; i < cntEntity; i++) { Device device = new Device(); device.setName("My device B" + i); device.setType("typeB"); devices.add(doPost("/api/device", device, Device.class)); } + + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Device(), new Device(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + testNotificationUpdateGatewayNever(); + for (int i = 0; i < 7; i++) { Device device = new Device(); device.setName("My device C" + i); @@ -192,7 +337,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { devices.add(doPost("/api/device", device, Device.class)); } List deviceTypes = doGetTyped("/api/device/types", - new TypeReference>() { + new TypeReference<>() { }); Assert.assertNotNull(deviceTypes); @@ -211,28 +356,52 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + doDelete("/api/device/" + savedDevice.getId().getId()) .andExpect(status().isOk()); - doGet("/api/device/" + savedDevice.getId().getId()) - .andExpect(status().isNotFound()); + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.DELETED, savedDevice.getId().getId().toString()); + testNotificationDeleteGatewayOneTime(savedDevice); + + EntityId savedDeviceId = savedDevice.getId(); + doGet("/api/device/" + savedDeviceId) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", savedDeviceId.getId().toString())))); } @Test public void testSaveDeviceWithEmptyType() throws Exception { Device device = new Device(); device.setName("My device"); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + Device savedDevice = doPost("/api/device", device, Device.class); Assert.assertEquals("default", savedDevice.getType()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + testNotificationUpdateGatewayNever(); } @Test public void testSaveDeviceWithEmptyName() throws Exception { Device device = new Device(); device.setType("default"); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + String msgError = "Device name " + msgErrorShouldBeSpecified; doPost("/api/device", device) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Device name should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(device, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + testNotificationUpdateGatewayNever(); } @Test @@ -246,17 +415,33 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { customer.setTitle("My customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId()); + testNotifyEntityAllOneTime(assignedDevice, assignedDevice.getId(), assignedDevice.getId(), savedTenant.getId(), + savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, + assignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(), + savedCustomer.getTitle()); + testNotificationUpdateGatewayNever(); + Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId()); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + Device unassignedDevice = doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); + testNotifyEntityAllOneTime(unassignedDevice, unassignedDevice.getId(), unassignedDevice.getId(), savedTenant.getId(), + savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, + unassignedDevice.getId().getId().toString(), savedCustomer.getId().getId().toString(), + savedCustomer.getTitle()); + testNotificationDeleteGatewayNever(); + foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId()); } @@ -267,9 +452,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setName("My device"); device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); - doPost("/api/customer/" + Uuids.timeBased().toString() - + "/device/" + savedDevice.getId().getId()) - .andExpect(status().isNotFound()); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + String customerIdStr = savedDevice.getId().toString(); + doPost("/api/customer/" + customerIdStr + + "/device/" + savedDevice.getId().getId()) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Customer", customerIdStr)))); + + testNotifyEntityNever(savedDevice.getId(), savedDevice); + testNotificationUpdateGatewayNever(); } @Test @@ -288,7 +481,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { tenantAdmin2.setFirstName("Joe"); tenantAdmin2.setLastName("Downs"); - tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); + createUserAndLogin(tenantAdmin2, "testPassword1"); Customer customer = new Customer(); customer.setTitle("Different customer"); @@ -301,9 +494,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + doPost("/api/customer/" + savedCustomer.getId().getId() + "/device/" + savedDevice.getId().getId()) - .andExpect(status().isForbidden()); + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedDevice.getId(), savedDevice); + testNotificationUpdateGatewayNever(); loginSysAdmin(); @@ -333,9 +532,16 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsId("access_token"); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + doPost("/api/device/credentials", deviceCredentials) .andExpect(status().isOk()); + testNotifyEntityMsgToEdgePushMsgToCoreOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(), + tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED, deviceCredentials); + testNotificationUpdateGatewayNever(); + DeviceCredentials foundDeviceCredentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); @@ -345,8 +551,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { @Test public void testSaveDeviceCredentialsWithEmptyDevice() throws Exception { DeviceCredentials deviceCredentials = new DeviceCredentials(); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + doPost("/api/device/credentials", deviceCredentials) - .andExpect(status().isBadRequest()); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Incorrect deviceId null"))); + + testNotifyEntityNever(deviceCredentials.getDeviceId(), new Device()); + testNotificationUpdateGatewayNever(); } @Test @@ -358,9 +571,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { DeviceCredentials deviceCredentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); deviceCredentials.setCredentialsType(null); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + String msgError = "Device credentials type " + msgErrorShouldBeSpecified; doPost("/api/device/credentials", deviceCredentials) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Device credentials type should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityIsNullOneTimeEdgeServiceNeverError(device, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED, + new DataValidationException(msgError), deviceCredentials); + testNotificationUpdateGatewayNever(); } @Test @@ -372,9 +594,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { DeviceCredentials deviceCredentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); deviceCredentials.setCredentialsId(null); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + String msgError = "Device credentials id " + msgErrorShouldBeSpecified; doPost("/api/device/credentials", deviceCredentials) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Device credentials id should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityIsNullOneTimeEdgeServiceNeverError(device, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED, + new DeviceCredentialsValidationException(msgError), deviceCredentials); + testNotificationUpdateGatewayNever(); } @Test @@ -390,9 +621,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { newDeviceCredentials.setDeviceId(deviceCredentials.getDeviceId()); newDeviceCredentials.setCredentialsType(deviceCredentials.getCredentialsType()); newDeviceCredentials.setCredentialsId(deviceCredentials.getCredentialsId()); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + String msgError = "Unable to update non-existent device credentials"; doPost("/api/device/credentials", newDeviceCredentials) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Unable to update non-existent device credentials"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityIsNullOneTimeEdgeServiceNeverError(device, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.CREDENTIALS_UPDATED, + new DeviceCredentialsValidationException(msgError), newDeviceCredentials); + testNotificationUpdateGatewayNever(); } @Test @@ -401,29 +641,44 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setName("My device"); device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); + DeviceId deviceTimeBasedId = new DeviceId(Uuids.timeBased()); DeviceCredentials deviceCredentials = doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - deviceCredentials.setDeviceId(new DeviceId(Uuids.timeBased())); + deviceCredentials.setDeviceId(deviceTimeBasedId); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + doPost("/api/device/credentials", deviceCredentials) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", deviceTimeBasedId.toString())))); + + testNotifyEntityNever(savedDevice.getId(), savedDevice); + testNotificationUpdateGatewayNever(); } @Test public void testFindTenantDevices() throws Exception { - log.debug("testFindTenantDevices"); - futures = new ArrayList<>(178); - for (int i = 0; i < 178; i++) { + int cntEntity = 178; + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + futures = new ArrayList<>(cntEntity); + for (int i = 0; i < cntEntity; i++) { Device device = new Device(); device.setName("Device" + i); device.setType("default"); futures.add(executor.submit(() -> doPost("/api/device", device, Device.class))); } - log.debug("await create devices"); + List devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - log.debug("start reading"); - List loadedDevices = new ArrayList<>(178); + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new Device(), new Device(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + testNotificationUpdateGatewayNever(); + + List loadedDevices = new ArrayList<>(cntEntity); PageLink pageLink = new PageLink(23); do { pageData = doGetTypedWithPageLink("/api/tenant/devices?", @@ -435,11 +690,16 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { } } while (pageData.hasNext()); - log.debug("asserting"); assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices); - log.debug("delete devices async"); + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + deleteEntitiesAsync("/api/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS); - log.debug("done"); + + testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Device(), new Device(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, cntEntity, 1); + testNotificationUpdateGatewayNever(); } @Test @@ -604,9 +864,12 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { customer.setTitle("Test customer"); customer = doPost("/api/customer", customer, Customer.class); CustomerId customerId = customer.getId(); + int cntEntity = 128; + + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); - futures = new ArrayList<>(128); - for (int i = 0; i < 128; i++) { + futures = new ArrayList<>(cntEntity); + for (int i = 0; i < cntEntity; i++) { Device device = new Device(); device.setName("Device" + i); device.setType("default"); @@ -618,7 +881,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { List devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS); - List loadedDevices = new ArrayList<>(128); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Device(), new Device(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity * 2); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + testNotificationUpdateGatewayNever(); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + List loadedDevices = new ArrayList<>(cntEntity); PageLink pageLink = new PageLink(23); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?", @@ -631,9 +901,12 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices); - log.debug("delete devices async"); deleteEntitiesAsync("/api/customer/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS); - log.debug("done"); + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Device(), new Device(), + savedTenant.getId(), customerId, tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UNASSIGNED_FROM_CUSTOMER, cntEntity, cntEntity, 3); + testNotificationUpdateGatewayNever(); } @Test @@ -843,16 +1116,30 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { createUserAndLogin(user, "testPassword1"); login("tenant2@thingsboard.org", "testPassword1"); - Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class); - doGet("/api/device/" + assignedDevice.getId().getId(), Device.class, status().isNotFound()); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + + Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + + savedDevice.getId().getId(), Device.class); + + doGet("/api/device/" + assignedDevice.getId().getId()) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", assignedDevice.getId().getId().toString())))); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(assignedDevice, assignedDevice.getId(), assignedDevice.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ASSIGNED_TO_TENANT, savedDifferentTenant.getId().getId().toString(), savedDifferentTenant.getTitle()); + testNotificationUpdateGatewayNever(); login("tenant9@thingsboard.org", "testPassword1"); Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId(), Device.class); Assert.assertNotNull(foundDevice1); - doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound()); + doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + + savedAnotherDevice.getId().getId() + "&toType=DEVICE") + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", savedAnotherDevice.getId().getId().toString())))); loginSysAdmin(); doDelete("/api/tenant/" + savedDifferentTenant.getId().getId()) @@ -869,20 +1156,51 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest { device.setType("default"); Device savedDevice = doPost("/api/device", device, Device.class); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + doPost("/api/edge/" + savedEdge.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class); + testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ASSIGNED_TO_EDGE, + savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName()); + testNotificationUpdateGatewayNever(); + pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?", PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100)); Assert.assertEquals(1, pageData.getData().size()); + Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService); + doDelete("/api/edge/" + savedEdge.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class); + testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_EDGE, savedDevice.getId().getId().toString(), savedEdge.getId().getId().toString(), savedEdge.getName()); + testNotificationUpdateGatewayNever(); + pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?", PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100)); Assert.assertEquals(0, pageData.getData().size()); } + + protected void testNotificationUpdateGatewayOneTime(Device device, Device oldDevice) { + Mockito.verify(gatewayNotificationsService, times(1)).onDeviceUpdated(Mockito.eq(device), Mockito.eq(oldDevice)); + } + + protected void testNotificationUpdateGatewayNever() { + Mockito.verify(gatewayNotificationsService, never()).onDeviceUpdated(Mockito.any(Device.class), Mockito.any(Device.class)); + } + + protected void testNotificationDeleteGatewayOneTime(Device device) { + Mockito.verify(gatewayNotificationsService, times(1)).onDeviceDeleted(device); + } + + protected void testNotificationDeleteGatewayNever() { + Mockito.verify(gatewayNotificationsService, never()).onDeviceDeleted(Mockito.any(Device.class)); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 8d088625d5..3fc461b44c 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -20,23 +20,30 @@ import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; +import org.mockito.Mockito; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; @@ -45,6 +52,8 @@ import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; public abstract class BaseDeviceProfileControllerTest extends AbstractControllerTest { @@ -84,6 +93,9 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController @Test public void testSaveDeviceProfile() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + + Mockito.reset(tbClusterService, auditLogService); + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); Assert.assertNotNull(savedDeviceProfile.getId()); @@ -94,36 +106,96 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault()); Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId()); Assert.assertEquals(DeviceProfileProvisionType.DISABLED, savedDeviceProfile.getProvisionType()); + + testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedDeviceProfile, savedDeviceProfile.getId(), savedDeviceProfile.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + savedDeviceProfile.setName("New device profile"); doPost("/api/deviceProfile", savedDeviceProfile, DeviceProfile.class); - DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); + + testNotifyEntityBroadcastEntityStateChangeEventOneTime(foundDeviceProfile, foundDeviceProfile.getId(), foundDeviceProfile.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED); } @Test public void saveDeviceProfileWithViolationOfValidation() throws Exception { - doPost("/api/deviceProfile", this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300))) - .andExpect(statusReason(containsString("length of name must be equal or less than 255"))); + String msgError = msgErrorFieldLength("name"); + + Mockito.reset(tbClusterService, auditLogService); + + DeviceProfile createDeviceProfile = this.createDeviceProfile(RandomStringUtils.randomAlphabetic(300)); + doPost("/api/deviceProfile", createDeviceProfile) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(createDeviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testFindDeviceProfileById() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertNotNull(foundDeviceProfile); Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); } + @Test + public void whenGetDeviceProfileById_thenPermissionsAreChecked() throws Exception { + DeviceProfile deviceProfile = createDeviceProfile("Device profile 1", null); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + + loginDifferentTenant(); + + doGet("/api/deviceProfile/" + deviceProfile.getId()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + } + @Test public void testFindDeviceProfileInfoById() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/"+savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); + DeviceProfileInfo foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/" + savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); Assert.assertNotNull(foundDeviceProfileInfo); Assert.assertEquals(savedDeviceProfile.getId(), foundDeviceProfileInfo.getId()); Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfileInfo.getName()); Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType()); + + Customer customer = new Customer(); + customer.setTitle("Customer"); + customer.setTenantId(savedTenant.getId()); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + + User customerUser = new User(); + customerUser.setAuthority(Authority.CUSTOMER_USER); + customerUser.setTenantId(savedTenant.getId()); + customerUser.setCustomerId(savedCustomer.getId()); + customerUser.setEmail("customer2@thingsboard.org"); + + createUserAndLogin(customerUser, "customer"); + + foundDeviceProfileInfo = doGet("/api/deviceProfileInfo/" + savedDeviceProfile.getId().getId().toString(), DeviceProfileInfo.class); + Assert.assertNotNull(foundDeviceProfileInfo); + Assert.assertEquals(savedDeviceProfile.getId(), foundDeviceProfileInfo.getId()); + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfileInfo.getName()); + Assert.assertEquals(savedDeviceProfile.getType(), foundDeviceProfileInfo.getType()); + } + + @Test + public void whenGetDeviceProfileInfoById_thenPermissionsAreChecked() throws Exception { + DeviceProfile deviceProfile = createDeviceProfile("Device profile 1", null); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + + loginDifferentTenant(); + doGet("/api/deviceProfileInfo/" + deviceProfile.getId()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); } @Test @@ -141,20 +213,35 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController public void testSetDefaultDeviceProfile() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile 1"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/"+savedDeviceProfile.getId().getId().toString()+"/default", DeviceProfile.class); + + Mockito.reset(tbClusterService, auditLogService); + + DeviceProfile defaultDeviceProfile = doPost("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString() + "/default", DeviceProfile.class); Assert.assertNotNull(defaultDeviceProfile); DeviceProfileInfo foundDefaultDeviceProfile = doGet("/api/deviceProfileInfo/default", DeviceProfileInfo.class); Assert.assertNotNull(foundDefaultDeviceProfile); Assert.assertEquals(savedDeviceProfile.getName(), foundDefaultDeviceProfile.getName()); Assert.assertEquals(savedDeviceProfile.getId(), foundDefaultDeviceProfile.getId()); Assert.assertEquals(savedDeviceProfile.getType(), foundDefaultDeviceProfile.getType()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(defaultDeviceProfile, defaultDeviceProfile.getId(), defaultDeviceProfile.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED); } @Test public void testSaveDeviceProfileWithEmptyName() throws Exception { DeviceProfile deviceProfile = new DeviceProfile(); - doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Device profile name should be specified"))); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Device profile name " + msgErrorShouldBeSpecified; + doPost("/api/deviceProfile", deviceProfile) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -162,8 +249,16 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile"); - doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Device profile with such name already exists"))); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Device profile with such name already exists"; + doPost("/api/deviceProfile", deviceProfile2) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -173,24 +268,33 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController doPost("/api/deviceProfile", deviceProfile).andExpect(status().isOk()); DeviceProfile deviceProfile2 = this.createDeviceProfile("Device Profile 2"); deviceProfile2.setProvisionDeviceKey("testProvisionDeviceKey"); - doPost("/api/deviceProfile", deviceProfile2).andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Device profile with such provision device key already exists"))); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Device profile with such provision device key already exists"; + doPost("/api/deviceProfile", deviceProfile2) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } - @Ignore @Test - public void testChangeDeviceProfileTypeWithExistingDevices() throws Exception { + public void testChangeDeviceProfileTypeNull() throws Exception { DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - Device device = new Device(); - device.setName("Test device"); - device.setType("default"); - device.setDeviceProfileId(savedDeviceProfile.getId()); - doPost("/api/device", device, Device.class); - //TODO uncomment once we have other device types; - //savedDeviceProfile.setType(DeviceProfileType.LWM2M); - doPost("/api/deviceProfile", savedDeviceProfile).andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Can't change device profile type because devices referenced it"))); + + Mockito.reset(tbClusterService, auditLogService); + + savedDeviceProfile.setType(null); + String msgError = "Device profile type " + msgErrorShouldBeSpecified; + doPost("/api/deviceProfile", savedDeviceProfile) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED, new DataValidationException(msgError)); } @Test @@ -202,9 +306,17 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController device.setType("default"); device.setDeviceProfileId(savedDeviceProfile.getId()); doPost("/api/device", device, Device.class); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Can't change device profile transport type because devices referenced it"; savedDeviceProfile.setTransportType(DeviceTransportType.MQTT); - doPost("/api/deviceProfile", savedDeviceProfile).andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Can't change device profile transport type because devices referenced it"))); + doPost("/api/deviceProfile", savedDeviceProfile) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED, new DataValidationException(msgError)); } @Test @@ -217,11 +329,89 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController device.setType("default"); device.setDeviceProfileId(savedDeviceProfile.getId()); - Device savedDevice = doPost("/api/device", device, Device.class); + doPost("/api/device", device, Device.class); + + Mockito.reset(tbClusterService, auditLogService); doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("The device profile referenced by the devices cannot be deleted"))); + + testNotifyEntityNever(savedDeviceProfile.getId(), savedDeviceProfile); + } + + @Test + public void testSaveDeviceProfileWithRuleChainFromDifferentTenant() throws Exception { + loginDifferentTenant(); + RuleChain ruleChain = new RuleChain(); + ruleChain.setName("Different rule chain"); + RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); + + loginTenantAdmin(); + + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setDefaultRuleChainId(savedRuleChain.getId()); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Can't assign rule chain from different tenant!"))); + } + + @Test + public void testSaveDeviceProfileWithDashboardFromDifferentTenant() throws Exception { + loginDifferentTenant(); + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("Different dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + + loginTenantAdmin(); + + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setDefaultDashboardId(savedDashboard.getId()); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Can't assign dashboard from different tenant!"))); + } + + @Test + public void testSaveDeviceProfileWithFirmwareFromDifferentTenant() throws Exception { + loginDifferentTenant(); + DeviceProfile differentProfile = createDeviceProfile("Different profile"); + differentProfile = doPost("/api/deviceProfile", differentProfile, DeviceProfile.class); + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); + firmwareInfo.setDeviceProfileId(differentProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("title"); + firmwareInfo.setVersion("1.0"); + firmwareInfo.setUrl("test.url"); + firmwareInfo.setUsesUrl(true); + OtaPackageInfo savedFw = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + + loginTenantAdmin(); + + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setFirmwareId(savedFw.getId()); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Can't assign firmware from different tenant!"))); + } + + @Test + public void testSaveDeviceProfileWithSoftwareFromDifferentTenant() throws Exception { + loginDifferentTenant(); + DeviceProfile differentProfile = createDeviceProfile("Different profile"); + differentProfile = doPost("/api/deviceProfile", differentProfile, DeviceProfile.class); + SaveOtaPackageInfoRequest softwareInfo = new SaveOtaPackageInfoRequest(); + softwareInfo.setDeviceProfileId(differentProfile.getId()); + softwareInfo.setType(SOFTWARE); + softwareInfo.setTitle("title"); + softwareInfo.setVersion("1.0"); + softwareInfo.setUrl("test.url"); + softwareInfo.setUsesUrl(true); + OtaPackageInfo savedSw = doPost("/api/otaPackage", softwareInfo, OtaPackageInfo.class); + + loginTenantAdmin(); + + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); + deviceProfile.setSoftwareId(savedSw.getId()); + doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Can't assign software from different tenant!"))); } @Test @@ -229,11 +419,19 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) .andExpect(status().isOk()); + String savedDeviceProfileIdFtr = savedDeviceProfile.getId().getId().toString(); + testNotifyEntityBroadcastEntityStateChangeEventOneTime(savedDeviceProfile, savedDeviceProfile.getId(), savedDeviceProfile.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, savedDeviceProfileIdFtr); + doGet("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString()) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device profile", savedDeviceProfileIdFtr)))); } @Test @@ -241,21 +439,31 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController List deviceProfiles = new ArrayList<>(); PageLink pageLink = new PageLink(17); PageData pageData = doGetTypedWithPageLink("/api/deviceProfiles?", - new TypeReference>(){}, pageLink); + new TypeReference<>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); deviceProfiles.addAll(pageData.getData()); - for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = 28; + for (int i = 0; i < cntEntity; i++) { + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile" + i); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new DeviceProfile(), new DeviceProfile(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, ActionType.ADDED, cntEntity, cntEntity, cntEntity); + Mockito.reset(tbClusterService, auditLogService); + List loadedDeviceProfiles = new ArrayList<>(); pageLink = new PageLink(17); do { pageData = doGetTypedWithPageLink("/api/deviceProfiles?", - new TypeReference>(){}, pageLink); + new TypeReference<>() { + }, pageLink); loadedDeviceProfiles.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -274,9 +482,14 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController } } + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(loadedDeviceProfiles.get(0), loadedDeviceProfiles.get(0), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, ActionType.DELETED, cntEntity, cntEntity, cntEntity, loadedDeviceProfiles.get(0).getId().getId().toString()); + pageLink = new PageLink(17); pageData = doGetTypedWithPageLink("/api/deviceProfiles?", - new TypeReference>(){}, pageLink); + new TypeReference<>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); } @@ -286,13 +499,14 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController List deviceProfiles = new ArrayList<>(); PageLink pageLink = new PageLink(17); PageData deviceProfilePageData = doGetTypedWithPageLink("/api/deviceProfiles?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); Assert.assertFalse(deviceProfilePageData.hasNext()); Assert.assertEquals(1, deviceProfilePageData.getTotalElements()); deviceProfiles.addAll(deviceProfilePageData.getData()); - for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile"+i); + for (int i = 0; i < 28; i++) { + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile" + i); deviceProfiles.add(doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class)); } @@ -301,7 +515,8 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController PageData pageData; do { pageData = doGetTypedWithPageLink("/api/deviceProfileInfos?", - new TypeReference>(){}, pageLink); + new TypeReference<>() { + }, pageLink); loadedDeviceProfileInfos.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -326,7 +541,8 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController pageLink = new PageLink(17); pageData = doGetTypedWithPageLink("/api/deviceProfileInfos?", - new TypeReference>(){}, pageLink); + new TypeReference>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); } @@ -705,7 +921,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Assert.assertTrue(savedDeviceProfile.getProfileData().getTransportConfiguration() instanceof MqttDeviceProfileTransportConfiguration); MqttDeviceProfileTransportConfiguration transportConfiguration = (MqttDeviceProfileTransportConfiguration) savedDeviceProfile.getProfileData().getTransportConfiguration(); Assert.assertTrue(transportConfiguration.isSendAckOnValidationException()); - DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+ savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); } @@ -715,7 +931,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); Assert.assertNotNull(savedDeviceProfile); - DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/"+ savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); + DeviceProfile foundDeviceProfile = doGet("/api/deviceProfile/" + savedDeviceProfile.getId().getId().toString(), DeviceProfile.class); Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); return savedDeviceProfile; } @@ -724,16 +940,30 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, null, null); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); - doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/deviceProfile", deviceProfile) + .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(errorMsg))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(errorMsg)); } private void testSaveDeviceProfileWithInvalidRpcRequestProtoSchema(String schema, String errorMsg) throws Exception { ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = this.createProtoTransportPayloadConfiguration(schema, schema, schema, null); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); - doPost("/api/deviceProfile", deviceProfile).andExpect(status().isBadRequest()) + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/deviceProfile", deviceProfile) + .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString(errorMsg))); + + testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(errorMsg)); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java index 24b2053c67..8bf763c637 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java @@ -18,11 +18,11 @@ package org.thingsboard.server.controller; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.StringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; @@ -30,18 +30,21 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; @@ -100,6 +103,9 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { @Test public void testSaveEdge() throws Exception { Edge edge = constructEdge("My edge", "default"); + + Mockito.reset(tbClusterService, auditLogService); + Edge savedEdge = doPost("/api/edge", edge, Edge.class); Assert.assertNotNull(savedEdge); @@ -110,23 +116,56 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId()); Assert.assertEquals(edge.getName(), savedEdge.getName()); + testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + savedEdge.setName("My new edge"); doPost("/api/edge", savedEdge, Edge.class); Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(foundEdge.getName(), savedEdge.getName()); + + testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(foundEdge, foundEdge.getId(), foundEdge.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED); } @Test public void testSaveEdgeWithViolationOfLengthValidation() throws Exception { Edge edge = constructEdge(RandomStringUtils.randomAlphabetic(300), "default"); - doPost("/api/edge", edge).andExpect(statusReason(containsString("length of name must be equal or less than 255"))); + String msgError = msgErrorFieldLength("name"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/edge", edge) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + + msgError = msgErrorFieldLength("type"); edge.setName("normal name"); edge.setType(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/edge", edge).andExpect(statusReason(containsString("length of type must be equal or less than 255"))); + doPost("/api/edge", edge) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + + msgError = msgErrorFieldLength("label"); edge.setType("normal type"); edge.setLabel(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/edge", edge).andExpect(statusReason(containsString("length of label must be equal or less than 255"))); + doPost("/api/edge", edge) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -141,10 +180,20 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { @Test public void testFindEdgeTypesByTenantId() throws Exception { List edges = new ArrayList<>(); - for (int i = 0; i < 3; i++) { + + int cntEntity = 3; + + Mockito.reset(tbClusterService, auditLogService); + + for (int i = 0; i < cntEntity; i++) { Edge edge = constructEdge("My edge B" + i, "typeB"); edges.add(doPost("/api/edge", edge, Edge.class)); } + + testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new Edge(), new Edge(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity, 0); + for (int i = 0; i < 7; i++) { Edge edge = constructEdge("My edge C" + i, "typeC"); edges.add(doPost("/api/edge", edge, Edge.class)); @@ -154,7 +203,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { edges.add(doPost("/api/edge", edge, Edge.class)); } List edgeTypes = doGetTyped("/api/edge/types", - new TypeReference>() { + new TypeReference<>() { }); Assert.assertNotNull(edgeTypes); @@ -169,27 +218,48 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Edge edge = constructEdge("My edge", "default"); Edge savedEdge = doPost("/api/edge", edge, Edge.class); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/edge/" + savedEdge.getId().getId().toString()) .andExpect(status().isOk()); + testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, savedEdge.getId().getId().toString()); + doGet("/api/edge/" + savedEdge.getId().getId().toString()) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Edge", savedEdge.getId().getId().toString())))); } @Test public void testSaveEdgeWithEmptyType() throws Exception { Edge edge = constructEdge("My edge", null); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Edge type " + msgErrorShouldBeSpecified; doPost("/api/edge", edge) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Edge type should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testSaveEdgeWithEmptyName() throws Exception { Edge edge = constructEdge(null, "default"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Edge name " + msgErrorShouldBeSpecified; doPost("/api/edge", edge) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Edge name should be specified"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(edge, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -201,10 +271,16 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { customer.setTitle("My customer"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + Mockito.reset(tbClusterService, auditLogService); + Edge assignedEdge = doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(savedCustomer.getId(), assignedEdge.getCustomerId()); + testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(assignedEdge, assignedEdge.getId(), assignedEdge.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ASSIGNED_TO_CUSTOMER, + assignedEdge.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); + Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(savedCustomer.getId(), foundEdge.getCustomerId()); @@ -212,6 +288,10 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { doDelete("/api/customer/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedEdge.getCustomerId().getId()); + testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(unassignedEdge, unassignedEdge.getId(), unassignedEdge.getId(), + savedTenant.getId(), savedCustomer.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UNASSIGNED_FROM_CUSTOMER, + unassignedEdge.getId().getId().toString(), savedCustomer.getId().getId().toString(), savedCustomer.getTitle()); + foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class); Assert.assertEquals(ModelConstants.NULL_UUID, foundEdge.getCustomerId().getId()); } @@ -221,9 +301,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Edge edge = constructEdge("My edge", "default"); Edge savedEdge = doPost("/api/edge", edge, Edge.class); - doPost("/api/customer/" + Uuids.timeBased().toString() - + "/edge/" + savedEdge.getId().getId().toString()) - .andExpect(status().isNotFound()); + Mockito.reset(tbClusterService, auditLogService); + + CustomerId customerId = new CustomerId(Uuids.timeBased()); + String customerIdStr = customerId.getId().toString(); + + String msgError = msgErrorNoFound("Customer", customerIdStr); + doPost("/api/customer/" + customerIdStr+ "/edge/" + savedEdge.getId().getId().toString()) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityNever(savedEdge.getId(), savedEdge); + testNotifyEntityNever(customerId, new Customer()); } @Test @@ -242,7 +331,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { tenantAdmin2.setFirstName("Joe"); tenantAdmin2.setLastName("Downs"); - tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1"); + createUserAndLogin(tenantAdmin2, "testPassword1"); Customer customer = new Customer(); customer.setTitle("Different customer"); @@ -253,9 +342,15 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Edge edge = constructEdge("My edge", "default"); Edge savedEdge = doPost("/api/edge", edge, Edge.class); + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/edge/" + savedEdge.getId().getId().toString()) - .andExpect(status().isForbidden()); + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedEdge.getId(), savedEdge); + testNotifyEntityNever(savedCustomer.getId(), savedCustomer); loginSysAdmin(); @@ -275,7 +370,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/edges?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedEdges.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -352,7 +447,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/tenant/edges?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -364,7 +459,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, title2); pageData = doGetTypedWithPageLink("/api/tenant/edges?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -460,14 +555,22 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { customer = doPost("/api/customer", customer, Customer.class); CustomerId customerId = customer.getId(); + Mockito.reset(tbClusterService, auditLogService); + List edges = new ArrayList<>(); - for (int i = 0; i < 128; i++) { + int cntEntity = 128; + for (int i = 0; i < cntEntity; i++) { Edge edge = constructEdge("Edge" + i, "default"); edge = doPost("/api/edge", edge, Edge.class); edges.add(doPost("/api/customer/" + customerId.getId().toString() + "/edge/" + edge.getId().getId().toString(), Edge.class)); } + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new Edge(), new Edge(), + savedTenant.getId(), customerId, tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ASSIGNED_TO_CUSTOMER, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, cntEntity * 2, + new String(), new String(), new String()); + List loadedEdges = new ArrayList<>(); PageLink pageLink = new PageLink(23); PageData pageData = null; @@ -518,7 +621,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { } List loadedEdgesTitle1 = new ArrayList<>(); - PageLink pageLink = new PageLink(15,0, title1); + PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?", @@ -536,7 +639,7 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(edgesTitle1, loadedEdgesTitle1); List loadedEdgesTitle2 = new ArrayList<>(); - pageLink = new PageLink(4,0, title2); + pageLink = new PageLink(4, 0, title2); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?", new TypeReference>() { @@ -552,11 +655,18 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Assert.assertEquals(edgesTitle2, loadedEdgesTitle2); + Mockito.reset(tbClusterService, auditLogService); + for (Edge edge : loadedEdgesTitle1) { doDelete("/api/customer/edge/" + edge.getId().getId().toString()) .andExpect(status().isOk()); } + int cntEntity = loadedEdgesTitle1.size(); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new Edge(), new Edge(), + savedTenant.getId(), customerId, tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UNASSIGNED_FROM_CUSTOMER, cntEntity, cntEntity, 3); + pageLink = new PageLink(4, 0, title1); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?", new TypeReference>() { @@ -690,10 +800,11 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret()); edgeImitator.ignoreType(UserCredentialsUpdateMsg.class); - edgeImitator.expectMessageAmount(11); + edgeImitator.expectMessageAmount(12); edgeImitator.connect(); assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue(); + assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("one msg during sync process").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("one msg during sync process, another from edge creation").hasSize(2); assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(1); @@ -701,10 +812,11 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4); - edgeImitator.expectMessageAmount(8); + edgeImitator.expectMessageAmount(9); doPost("/api/edge/sync/" + edge.getId()); assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue(); + assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("rule chain msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1); @@ -714,7 +826,8 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { edgeImitator.allowIgnoredTypes(); try { edgeImitator.disconnect(); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } doDelete("/api/device/" + savedDevice.getId().getId().toString()) .andExpect(status().isOk()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java new file mode 100644 index 0000000000..2d28e32906 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityRelationControllerTest.java @@ -0,0 +1,631 @@ +/** + * 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 com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntityRelationInfo; +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.relation.RelationsSearchParameters; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.relation.RelationService; + +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.Matchers.containsString; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@Slf4j +public abstract class BaseEntityRelationControllerTest extends AbstractControllerTest { + + public static final String BASE_DEVICE_NAME = "Test dummy device"; + + @Autowired + RelationService relationService; + + private IdComparator idComparator; + private Tenant savedTenant; + private User tenantAdmin; + private Device mainDevice; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + idComparator = new IdComparator<>(); + + Tenant tenant = new Tenant(); + tenant.setTitle("Test tenant"); + + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + Device device = new Device(); + device.setName("Main test device"); + device.setType("default"); + mainDevice = doPost("/api/device", device, Device.class); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveAndFindRelation() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", device.getUuidId(), EntityType.DEVICE + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + + testNotifyEntityAllOneTimeRelation(foundRelation, + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATION_ADD_OR_UPDATE, foundRelation); + } + + @Test + public void testSaveWithDeviceFromNotCreated() throws Exception { + Device device = new Device(); + device.setName("Test device 2"); + device.setType("default"); + EntityRelation relation = createFromRelation(device, mainDevice, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/relation", relation) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Parameter entityId can't be empty!"))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + + @Test + public void testSaveWithDeviceToNotCreated() throws Exception { + Device device = new Device(); + device.setName("Test device 2"); + device.setType("default"); + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/relation", relation) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Parameter entityId can't be empty!"))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + + @Test + public void testSaveWithDeviceToMissing() throws Exception { + Device device = new Device(); + device.setName("Test device 2"); + device.setType("default"); + device.setId(new DeviceId(UUID.randomUUID())); + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/relation", relation) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", device.getId().getId().toString())))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + + @Test + public void testSaveAndFindRelationsByFrom() throws Exception { + final int numOfDevices = 30; + + Mockito.reset(tbClusterService, auditLogService); + + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + EntityRelation relationTest = createFromRelation(mainDevice, mainDevice, "TEST_NOTIFY_ENTITY"); + testNotifyEntityAllManyRelation(relationTest, savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATION_ADD_OR_UPDATE, numOfDevices); + + String url = String.format("/api/relations?fromId=%s&fromType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + assertFoundList(url, numOfDevices); + } + + @Test + public void testSaveAndFindRelationsByTo() throws Exception { + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + String url = String.format("/api/relations?toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + assertFoundList(url, numOfDevices); + } + + @Test + public void testSaveAndFindRelationsByFromWithRelationType() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(mainDevice, device, relationType); + + doPost("/api/relation", relation).andExpect(status().isOk()); + String url = String.format("/api/relations?fromId=%s&fromType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, relationType + ); + + assertFoundList(url, 1); + } + + @Test + public void testSaveAndFindRelationsByFromWithRelationTypeOther() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(mainDevice, device, relationType); + + doPost("/api/relation", relation).andExpect(status().isOk()); + + String relationTypeOther = "TEST_OTHER"; + String url = String.format("/api/relations?fromId=%s&fromType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, relationTypeOther + ); + + assertFoundList(url, 0); + } + + @Test + public void testSaveAndFindRelationsByToWithRelationType() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(device, mainDevice, relationType); + + doPost("/api/relation", relation).andExpect(status().isOk()); + String url = String.format("/api/relations?toId=%s&toType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, relationType + ); + + assertFoundList(url, 1); + } + + + @Test + public void testSaveAndFindRelationsByToWithRelationTypeOther() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + Device device = buildSimpleDevice("Unique dummy test device "); + String relationType = "TEST"; + EntityRelation relation = createFromRelation(device, mainDevice, relationType); + + doPost("/api/relation", relation).andExpect(status().isOk()); + + String relationTypeOther = "TEST_OTHER"; + String url = String.format("/api/relations?toId=%s&toType=%s&relationType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, relationTypeOther + ); + + assertFoundList(url, 0); + } + + @Test + public void testFindRelationsInfoByFrom() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + String url = String.format("/api/relations/info?fromId=%s&fromType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + List relationsInfos = + JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference<>() { + }); + + Assert.assertNotNull("Relations is not found!", relationsInfos); + Assert.assertEquals("List of found relationsInfos is not equal to number of created relations!", + numOfDevices, relationsInfos.size()); + + assertRelationsInfosByFrom(relationsInfos); + } + + @Test + public void testFindRelationsInfoByTo() throws Exception { + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + String url = String.format("/api/relations/info?toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + List relationsInfos = + JacksonUtil.convertValue(doGet(url, JsonNode.class), new TypeReference<>() { + }); + + Assert.assertNotNull("Relations is not found!", relationsInfos); + Assert.assertEquals("List of found relationsInfos is not equal to number of created relations!", + numOfDevices, relationsInfos.size()); + + assertRelationsInfosByTo(relationsInfos); + } + + @Test + public void testDeleteRelation() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", device.getUuidId(), EntityType.DEVICE + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + + Mockito.reset(tbClusterService, auditLogService); + + doDelete(url).andExpect(status().isOk()); + + testNotifyEntityAllOneTimeRelation(foundRelation, + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATION_DELETED, foundRelation); + + doGet(url).andExpect(status().is4xxClientError()); + } + + @Test + public void testDeleteRelationWithOtherFromDeviceError() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + Device device2 = buildSimpleDevice("Test device 2"); + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + device2.getUuidId(), EntityType.DEVICE, + "CONTAINS", device.getUuidId(), EntityType.DEVICE + ); + + Mockito.reset(tbClusterService, auditLogService); + + doDelete(url) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNotFound))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + + @Test + public void testDeleteRelationWithOtherToDeviceError() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + Device device2 = buildSimpleDevice("Test device 2"); + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", device2.getUuidId(), EntityType.DEVICE + ); + + Mockito.reset(tbClusterService, auditLogService); + + doDelete(url) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNotFound))); + + testNotifyEntityNever(mainDevice.getId(), null); + } + + @Test + public void testDeleteRelations() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME + " from"); + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME + " to"); + + String urlTo = String.format("/api/relations?toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + String urlFrom = String.format("/api/relations?fromId=%s&fromType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + assertFoundList(urlTo, numOfDevices); + assertFoundList(urlFrom, numOfDevices); + + String url = String.format("/api/relations?entityId=%s&entityType=%s", + mainDevice.getUuidId(), EntityType.DEVICE + ); + + Mockito.reset(tbClusterService, auditLogService); + + doDelete(url).andExpect(status().isOk()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(null, mainDevice.getId(), mainDevice.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.RELATIONS_DELETED); + + Assert.assertTrue( + "Performed deletion of all relations but some relations were found!", + doGet(urlTo, List.class).isEmpty() + ); + Assert.assertTrue( + "Performed deletion of all relations but some relations were found!", + doGet(urlFrom, List.class).isEmpty() + ); + } + + @Test + public void testFindRelationsByFromQuery() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.FROM, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relations = readResponse( + doPost("/api/relations", query).andExpect(status().isOk()), + new TypeReference>() { + } + ); + + assertFoundRelations(relations, numOfDevices); + } + + @Test + public void testFindRelationsByToQuery() throws Exception { + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.TO, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relations = readResponse( + doPost("/api/relations", query).andExpect(status().isOk()), + new TypeReference<>() { + } + ); + + assertFoundRelations(relations, numOfDevices); + } + + @Test + public void testFindRelationsInfoByFromQuery() throws Exception { + final int numOfDevices = 30; + createDevicesByFrom(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.FROM, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relationsInfo = readResponse( + doPost("/api/relations/info", query).andExpect(status().isOk()), + new TypeReference<>() { + } + ); + + assertRelationsInfosByFrom(relationsInfo); + } + + @Test + public void testFindRelationsInfoByToQuery() throws Exception { + final int numOfDevices = 30; + createDevicesByTo(numOfDevices, BASE_DEVICE_NAME); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters( + mainDevice.getUuidId(), EntityType.DEVICE, + EntitySearchDirection.TO, + RelationTypeGroup.COMMON, + 1, true + )); + query.setFilters(Collections.singletonList( + new RelationEntityTypeFilter("CONTAINS", List.of(EntityType.DEVICE)) + )); + + List relationsInfo = readResponse( + doPost("/api/relations/info", query).andExpect(status().isOk()), + new TypeReference<>() { + } + ); + + assertRelationsInfosByTo(relationsInfo); + } + + @Test + public void testCreateRelationFromTenantToDevice() throws Exception { + EntityRelation relation = new EntityRelation(tenantAdmin.getTenantId(), mainDevice.getId(), "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + tenantAdmin.getTenantId(), EntityType.TENANT, + "CONTAINS", mainDevice.getUuidId(), EntityType.DEVICE + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + } + + @Test + public void testCreateRelationFromDeviceToTenant() throws Exception { + EntityRelation relation = new EntityRelation(mainDevice.getId(), tenantAdmin.getTenantId(), "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", tenantAdmin.getTenantId(), EntityType.TENANT + ); + + EntityRelation foundRelation = doGet(url, EntityRelation.class); + + Assert.assertNotNull("Relation is not found!", foundRelation); + Assert.assertEquals("Found relation is not equals origin!", relation, foundRelation); + } + + @Test + public void testSaveAndFindRelationDifferentTenant() throws Exception { + Device device = buildSimpleDevice("Test device 1"); + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + + doPost("/api/relation", relation).andExpect(status().isOk()); + + String url = String.format("/api/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", + mainDevice.getUuidId(), EntityType.DEVICE, + "CONTAINS", device.getUuidId(), EntityType.DEVICE + ); + + loginDifferentTenant(); + + doGet(url) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Device", relation.getFrom().getId().toString())))); + + deleteDifferentTenant(); + } + + private Device buildSimpleDevice(String name) throws Exception { + Device device = new Device(); + device.setName(name); + device.setType("default"); + device = doPost("/api/device", device, Device.class); + return device; + } + + private EntityRelation createFromRelation(Device mainDevice, Device device, String relationType) { + return new EntityRelation(mainDevice.getId(), device.getId(), relationType); + } + + private void createDevicesByFrom(int numOfDevices, String baseName) throws Exception { + for (int i = 0; i < numOfDevices; i++) { + Device device = buildSimpleDevice(baseName + i); + + EntityRelation relation = createFromRelation(mainDevice, device, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + } + } + + private void createDevicesByTo(int numOfDevices, String baseName) throws Exception { + for (int i = 0; i < numOfDevices; i++) { + Device device = buildSimpleDevice(baseName + i); + EntityRelation relation = createFromRelation(device, mainDevice, "CONTAINS"); + doPost("/api/relation", relation).andExpect(status().isOk()); + } + } + + private void assertFoundRelations(List relations, int numOfDevices) { + Assert.assertNotNull("Relations is not found!", relations); + Assert.assertEquals("List of found relations is not equal to number of created relations!", + numOfDevices, relations.size()); + } + + private void assertFoundList(String url, int numOfDevices) throws Exception { + @SuppressWarnings("unchecked") + List relations = doGet(url, List.class); + assertFoundRelations(relations, numOfDevices); + } + + private void assertRelationsInfosByFrom(List relationsInfos) { + for (EntityRelationInfo info : relationsInfos) { + Assert.assertEquals("Wrong FROM entityId!", mainDevice.getId(), info.getFrom()); + Assert.assertTrue("Wrong FROM name!", info.getToName().contains(BASE_DEVICE_NAME)); + Assert.assertEquals("Wrong relationType!", "CONTAINS", info.getType()); + } + } + + private void assertRelationsInfosByTo(List relationsInfos) { + for (EntityRelationInfo info : relationsInfos) { + Assert.assertEquals("Wrong TO entityId!", mainDevice.getId(), info.getTo()); + Assert.assertTrue("Wrong TO name!", info.getFromName().contains(BASE_DEVICE_NAME)); + Assert.assertEquals("Wrong relationType!", "CONTAINS", info.getType()); + } + } +} diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java index 615914c6d7..f1121dfb14 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java @@ -32,6 +32,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.objects.AttributesEntityView; @@ -52,6 +54,7 @@ import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; import java.util.ArrayList; @@ -92,7 +95,6 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Before public void beforeTest() throws Exception { - log.debug("beforeTest"); executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); loginTenantAdmin(); @@ -126,6 +128,9 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes @Test public void testSaveEntityView() throws Exception { String name = "Test entity view"; + + Mockito.reset(tbClusterService, auditLogService); + EntityView savedView = getNewSavedEntityView(name); Assert.assertNotNull(savedView); @@ -140,6 +145,12 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes assertEquals(savedView, foundEntityView); + testBroadcastEntityStateChangeEventTime(foundEntityView.getId(), tenantId, 1); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundEntityView, foundEntityView, + tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.ADDED, ActionType.ADDED, 1, 0, 1); + Mockito.reset(tbClusterService, auditLogService); + savedView.setName("New test entity view"); doPost("/api/entityView", savedView, EntityView.class); @@ -147,23 +158,57 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes assertEquals(savedView, foundEntityView); - doGet("/api/tenant/entityViews?entityViewName=" + name, EntityView.class, status().isNotFound()); + testBroadcastEntityStateChangeEventTime(foundEntityView.getId(), tenantId, 1); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundEntityView, foundEntityView, + tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.UPDATED, ActionType.UPDATED, 1, 1, 5); + + doGet("/api/tenant/entityViews?entityViewName=" + name) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNotFound))); } @Test public void testSaveEntityViewWithViolationOfValidation() throws Exception { EntityView entityView = createEntityView(RandomStringUtils.randomAlphabetic(300), 0, 0); - doPost("/api/entityView", entityView).andExpect(statusReason(containsString("length of name must be equal or less than 255"))); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = msgErrorFieldLength("name"); + doPost("/api/entityView", entityView) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(entityView, + tenantId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + entityView.setName("Normal name"); + msgError = msgErrorFieldLength("type"); entityView.setType(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/entityView", entityView).andExpect(statusReason(containsString("length of type must be equal or less than 255"))); + doPost("/api/entityView", entityView) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(entityView, + tenantId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testUpdateEntityViewFromDifferentTenant() throws Exception { EntityView savedView = getNewSavedEntityView("Test entity view"); loginDifferentTenant(); - doPost("/api/entityView", savedView, EntityView.class, status().isForbidden()); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/entityView", savedView) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedView.getId(), savedView); + deleteDifferentTenant(); } @@ -174,20 +219,36 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes view.setCustomerId(customer.getId()); EntityView savedView = doPost("/api/entityView", view, EntityView.class); - doDelete("/api/entityView/" + savedView.getId().getId().toString()) + Mockito.reset(tbClusterService, auditLogService); + + String entityIdStr = savedView.getId().getId().toString(); + doDelete("/api/entityView/" + entityIdStr) .andExpect(status().isOk()); - doGet("/api/entityView/" + savedView.getId().getId().toString()) - .andExpect(status().isNotFound()); + testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedView, savedView.getId(), savedView.getId(), + tenantId, view.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.DELETED, entityIdStr); + + doGet("/api/entityView/" + entityIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Entity view",entityIdStr)))); } @Test public void testSaveEntityViewWithEmptyName() throws Exception { EntityView entityView = new EntityView(); entityView.setType("default"); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = "Entity view name " + msgErrorShouldBeSpecified; doPost("/api/entityView", entityView) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Entity view name should be specified!"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(entityView, + tenantId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -195,8 +256,17 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes EntityView view = getNewSavedEntityView("Test entity view"); Customer savedCustomer = doPost("/api/customer", getNewCustomer("My customer"), Customer.class); view.setCustomerId(savedCustomer.getId()); + + Mockito.reset(tbClusterService, auditLogService); + EntityView savedView = doPost("/api/entityView", view, EntityView.class); + testBroadcastEntityStateChangeEventTime(savedView.getId(), tenantId, 1); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedView, savedView, + tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.UPDATED, ActionType.UPDATED, 1, 1, 5); + Mockito.reset(tbClusterService, auditLogService); + EntityView assignedView = doPost( "/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString(), EntityView.class); @@ -205,18 +275,38 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes EntityView foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); assertEquals(savedCustomer.getId(), foundView.getCustomerId()); + testBroadcastEntityStateChangeEventNever(foundView.getId()); + testNotifyEntityAllOneTime(foundView, foundView.getId(), foundView.getId(), + tenantId, foundView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.ASSIGNED_TO_CUSTOMER, + foundView.getId().getId().toString(), foundView.getCustomerId().getId().toString(), savedCustomer.getTitle()); + EntityView unAssignedView = doDelete("/api/customer/entityView/" + savedView.getId().getId().toString(), EntityView.class); assertEquals(ModelConstants.NULL_UUID, unAssignedView.getCustomerId().getId()); foundView = doGet("/api/entityView/" + savedView.getId().getId().toString(), EntityView.class); assertEquals(ModelConstants.NULL_UUID, foundView.getCustomerId().getId()); + + testBroadcastEntityStateChangeEventNever(foundView.getId()); + testNotifyEntityAllOneTime(unAssignedView, savedView.getId(), savedView.getId(), + tenantId, savedView.getCustomerId(), tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.UNASSIGNED_FROM_CUSTOMER, + savedView.getCustomerId().getId().toString(), savedCustomer.getTitle()); } @Test public void testAssignEntityViewToNonExistentCustomer() throws Exception { EntityView savedView = getNewSavedEntityView("Test entity view"); - doPost("/api/customer/" + Uuids.timeBased().toString() + "/device/" + savedView.getId().getId().toString()) - .andExpect(status().isNotFound()); + + Mockito.reset(tbClusterService, auditLogService); + + String customerIdStr = Uuids.timeBased().toString(); + String msgError = msgErrorNoFound("Customer", customerIdStr); + doPost("/api/customer/" + customerIdStr + "/device/" + savedView.getId().getId().toString()) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityNever(savedView.getId(), savedView); } @Test @@ -242,8 +332,13 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes EntityView savedView = getNewSavedEntityView("Test entity view"); + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/entityView/" + savedView.getId().getId().toString()) - .andExpect(status().isForbidden()); + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedView.getId(), savedView); loginSysAdmin(); @@ -257,8 +352,11 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes CustomerId customerId = customer.getId(); String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViewInfos?"; - List> viewFutures = new ArrayList<>(128); - for (int i = 0; i < 128; i++) { + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = 128; + List> viewFutures = new ArrayList<>(cntEntity); + for (int i = 0; i < cntEntity; i++) { String entityName = "Test entity view " + i; viewFutures.add(executor.submit(() -> new EntityViewInfo(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/" @@ -269,6 +367,15 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes List loadedViews = loadListOfInfo(new PageLink(23), urlTemplate); assertThat(entityViewInfos).containsExactlyInAnyOrderElementsOf(loadedViews); + + testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), + tenantId, tenantAdminCustomerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity*2, 0); + + testNotifyEntityBroadcastEntityStateChangeEventMany(new EntityView(), new EntityView(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.ASSIGNED_TO_CUSTOMER, ActionType.ASSIGNED_TO_CUSTOMER, cntEntity, cntEntity, + cntEntity*2, 3); } @Test @@ -289,12 +396,21 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes assertThat(namesOfView2).as(name2).containsExactlyInAnyOrderElementsOf(loadedNamesOfView2); deleteFutures.clear(); + + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = loadedNamesOfView1.size(); for (EntityView view : loadedNamesOfView1) { deleteFutures.add(executor.submit(() -> doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()))); } Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS); + testBroadcastEntityStateChangeEventNever(loadedNamesOfView1.get(0).getId()); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new EntityView(), new EntityView(), + tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, + ActionType.UNASSIGNED_FROM_CUSTOMER, ActionType.UNASSIGNED_FROM_CUSTOMER, cntEntity, cntEntity, 2); + PageData pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(4, 0, name1)); Assert.assertFalse(pageData.hasNext()); @@ -370,10 +486,8 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes Set expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4"); Set actualAttributesSet = putAttributesAndWait("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet); - log.debug("got correct actualAttributesSet, saving new entity view..."); EntityView savedView = getNewSavedEntityView("Test entity view"); - log.debug("fetching entity view telemetry..."); List> values = await("telemetry/ENTITY_VIEW") .atMost(TIMEOUT, SECONDS) .until(() -> doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + @@ -381,7 +495,6 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes }), x -> x.size() >= expectedActualAttributesSet.size()); - log.debug("asserting..."); assertEquals("value1", getValue(values, "caKey1")); assertEquals(true, getValue(values, "caKey2")); assertEquals(42.0, getValue(values, "caKey3")); @@ -526,7 +639,6 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes getWsClient().subscribeLatestUpdate(keysToSubscribe, dtf); String viewDeviceId = testDevice.getId().getId().toString(); - log.debug("deviceid {}", viewDeviceId); DeviceCredentials deviceCredentials = doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class); assertEquals(testDevice.getId(), deviceCredentials.getDeviceId()); @@ -534,7 +646,6 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes String accessToken = deviceCredentials.getCredentialsId(); assertNotNull(accessToken); - log.debug("creating mqtt client..."); String clientId = MqttAsyncClient.generateClientId(); MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence()); @@ -542,14 +653,11 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes options.setUserName(accessToken); client.connect(options); awaitConnected(client, SECONDS.toMillis(30)); - log.debug("mqtt connected..."); MqttMessage message = new MqttMessage(); message.setPayload((stringKV).getBytes()); getWsClient().registerWaitForUpdate(); IMqttDeliveryToken token = client.publish("v1/devices/me/attributes", message); - log.debug("publish token.message {}", token.getMessage()); await("mqtt ack").pollInterval(5, MILLISECONDS).atMost(TIMEOUT, SECONDS).until(() -> token.getMessage() == null); - log.debug("token.message delivered {}", token.getMessage()); assertThat(getWsClient().waitForUpdate()).as("ws update received").isNotBlank(); return getAttributeKeys("DEVICE", viewDeviceId); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java index cbc00b0987..18d76d96e8 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java @@ -21,6 +21,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; @@ -31,10 +32,12 @@ import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -102,6 +105,8 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes firmwareInfo.setVersion(VERSION); firmwareInfo.setUsesUrl(false); + Mockito.reset(tbClusterService, auditLogService); + OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); Assert.assertNotNull(savedFirmwareInfo); @@ -111,12 +116,20 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); Assert.assertEquals(firmwareInfo.getVersion(), savedFirmwareInfo.getVersion()); + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedFirmwareInfo, savedFirmwareInfo.getId(), savedFirmwareInfo.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); save(new SaveOtaPackageInfoRequest(savedFirmwareInfo, false)); OtaPackageInfo foundFirmwareInfo = doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString(), OtaPackageInfo.class); Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(foundFirmwareInfo, foundFirmwareInfo.getId(), foundFirmwareInfo.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED); } @Test @@ -127,14 +140,42 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes firmwareInfo.setTitle(RandomStringUtils.randomAlphabetic(300)); firmwareInfo.setVersion(VERSION); firmwareInfo.setUsesUrl(false); - doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of title must be equal or less than 255"))); + String msgError = msgErrorFieldLength("title"); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/otaPackage", firmwareInfo) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + firmwareInfo.setTenantId(savedTenant.getId()); + testNotifyEntityEqualsOneTimeServiceNeverError(firmwareInfo, + savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, new DataValidationException(msgError)); + firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of version must be equal or less than 255"))); + msgError = msgErrorFieldLength("version"); + doPost("/api/otaPackage", firmwareInfo) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + firmwareInfo.setTenantId(savedTenant.getId()); + testNotifyEntityEqualsOneTimeServiceNeverError(firmwareInfo, + savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, new DataValidationException(msgError)); + firmwareInfo.setVersion(VERSION); firmwareInfo.setUsesUrl(true); + msgError = msgErrorFieldLength("url"); firmwareInfo.setUrl(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/otaPackage", firmwareInfo).andExpect(statusReason(containsString("length of url must be equal or less than 255"))); + doPost("/api/otaPackage", firmwareInfo) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + firmwareInfo.setTenantId(savedTenant.getId()); + testNotifyEntityEqualsOneTimeServiceNeverError(firmwareInfo, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -164,12 +205,19 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + Mockito.reset(tbClusterService, auditLogService); + + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); Assert.assertEquals(FILE_NAME, savedFirmware.getFileName()); Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType()); Assert.assertEquals(CHECKSUM_ALGORITHM, savedFirmware.getChecksumAlgorithm().name()); Assert.assertEquals(CHECKSUM, savedFirmware.getChecksum()); + + testNotifyEntityAllOneTime(savedFirmware, savedFirmware.getId(), savedFirmware.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED); } @Test @@ -184,10 +232,16 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); loginDifferentTenant(); + + Mockito.reset(tbClusterService, auditLogService); + doPost("/api/otaPackage", - new SaveOtaPackageInfoRequest(savedFirmwareInfo, false), - OtaPackageInfo.class, - status().isForbidden()); + new SaveOtaPackageInfoRequest(savedFirmwareInfo, false)) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedFirmwareInfo.getId(), savedFirmwareInfo); + deleteDifferentTenant(); } @@ -220,11 +274,12 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); OtaPackage foundFirmware = doGet("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString(), OtaPackage.class); Assert.assertNotNull(foundFirmware); - Assert.assertEquals(savedFirmware, new OtaPackageInfo(foundFirmware)); + Assert.assertEquals(savedFirmware, foundFirmware); Assert.assertEquals(DATA, foundFirmware.getData()); } @@ -239,17 +294,29 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString()) .andExpect(status().isOk()); + testNotifyEntityAllOneTime(savedFirmwareInfo, savedFirmwareInfo.getId(), savedFirmwareInfo.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, savedFirmwareInfo.getId().getId().toString()); + doGet("/api/otaPackage/info/" + savedFirmwareInfo.getId().getId().toString()) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNotFound))); } @Test public void testFindTenantFirmwares() throws Exception { + + Mockito.reset(tbClusterService, auditLogService); + List otaPackages = new ArrayList<>(); - for (int i = 0; i < 165; i++) { + int cntEntity = 165; + int startIndexSaveData = 101; + for (int i = 0; i < cntEntity; i++) { SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); @@ -259,16 +326,19 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes OtaPackageInfo savedFirmwareInfo = save(firmwareInfo); - if (i > 100) { + if (i >= startIndexSaveData) { MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); - otaPackages.add(savedFirmware); - } else { - otaPackages.add(savedFirmwareInfo); + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + savedFirmwareInfo = new OtaPackageInfo(savedFirmware); } + otaPackages.add(savedFirmwareInfo); } + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new OtaPackageInfo(), new OtaPackageInfo(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, ActionType.ADDED, cntEntity, 0, (cntEntity*2 - startIndexSaveData)); + List loadedFirmwares = new ArrayList<>(); PageLink pageLink = new PageLink(24); PageData pageData; @@ -306,7 +376,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes if (i > 100) { MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); - OtaPackageInfo savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); savedFirmwareInfo = new OtaPackageInfo(savedFirmware); otaPackagesWithData.add(savedFirmwareInfo); } @@ -352,11 +422,11 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); } - protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { + protected OtaPackage savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); postRequest.file(content); setJwtToken(postRequest); - return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackage.class); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java index 9395f99366..33602bbfa9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseRuleChainControllerTest.java @@ -21,14 +21,17 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; @@ -75,22 +78,45 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest public void testSaveRuleChain() throws Exception { RuleChain ruleChain = new RuleChain(); ruleChain.setName("RuleChain"); + + Mockito.reset(tbClusterService, auditLogService); + RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); Assert.assertNotNull(savedRuleChain); Assert.assertNotNull(savedRuleChain.getId()); Assert.assertTrue(savedRuleChain.getCreatedTime() > 0); Assert.assertEquals(ruleChain.getName(), savedRuleChain.getName()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedRuleChain, savedRuleChain.getId(), savedRuleChain.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + savedRuleChain.setName("New RuleChain"); doPost("/api/ruleChain", savedRuleChain, RuleChain.class); RuleChain foundRuleChain = doGet("/api/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); Assert.assertEquals(savedRuleChain.getName(), foundRuleChain.getName()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedRuleChain, savedRuleChain.getId(), savedRuleChain.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED); } @Test public void testSaveRuleChainWithViolationOfLengthValidation() throws Exception { + + Mockito.reset(tbClusterService, auditLogService); + RuleChain ruleChain = new RuleChain(); ruleChain.setName(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/ruleChain", ruleChain).andExpect(statusReason(containsString("length of name must be equal or less than 255"))); + String msgError = msgErrorFieldLength("name"); + doPost("/api/ruleChain", ruleChain) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + ruleChain.setTenantId(savedTenant.getId()); + testNotifyEntityEqualsOneTimeServiceNeverError(ruleChain, + savedTenant.getId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -109,11 +135,19 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest ruleChain.setName("RuleChain"); RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); + Mockito.reset(tbClusterService, auditLogService); + + String entityIdStr = savedRuleChain.getId().getId().toString(); doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString()) .andExpect(status().isOk()); - doGet("/api/ruleChain/" + savedRuleChain.getId().getId().toString()) - .andExpect(status().isNotFound()); + testNotifyEntityBroadcastEntityStateChangeEventOneTimeMsgToEdgeServiceNever(savedRuleChain, savedRuleChain.getId(), savedRuleChain.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, savedRuleChain.getId().getId().toString()); + + doGet("/api/ruleChain/" + entityIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Rule chain", entityIdStr)))); } @Test @@ -121,15 +155,20 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest Edge edge = constructEdge("My edge", "default"); Edge savedEdge = doPost("/api/edge", edge, Edge.class); + List edgeRuleChains = new ArrayList<>(); PageLink pageLink = new PageLink(17); PageData pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/ruleChains?", - new TypeReference<>() {}, pageLink); + new TypeReference<>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); edgeRuleChains.addAll(pageData.getData()); - for (int i = 0; i < 28; i++) { + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = 28; + for (int i = 0; i < cntEntity; i++) { RuleChain ruleChain = new RuleChain(); ruleChain.setName("RuleChain " + i); ruleChain.setType(RuleChainType.EDGE); @@ -139,11 +178,21 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest edgeRuleChains.add(savedRuleChain); } + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new RuleChain(), new RuleChain(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, ActionType.ADDED, cntEntity, 0, cntEntity * 2); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new RuleChain(), new RuleChain(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ASSIGNED_TO_EDGE, ActionType.ASSIGNED_TO_EDGE, cntEntity, cntEntity, cntEntity * 2, + new String(), new String(), new String()); + Mockito.reset(tbClusterService, auditLogService); + List loadedEdgeRuleChains = new ArrayList<>(); pageLink = new PageLink(17); do { pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/ruleChains?", - new TypeReference<>() {}, pageLink); + new TypeReference<>() { + }, pageLink); loadedEdgeRuleChains.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -162,9 +211,14 @@ public abstract class BaseRuleChainControllerTest extends AbstractControllerTest } } + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAnyAdditionalInfoAny(new RuleChain(), new RuleChain(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UNASSIGNED_FROM_EDGE, ActionType.UNASSIGNED_FROM_EDGE, cntEntity, cntEntity, 3); + pageLink = new PageLink(17); pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/ruleChains?", - new TypeReference<>() {}, pageLink); + new TypeReference<>() { + }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java index 4088e78563..d3153f57bc 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTbResourceControllerTest.java @@ -21,14 +21,17 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; @@ -75,6 +78,9 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes @Test public void testSaveTbResource() throws Exception { + + Mockito.reset(tbClusterService, auditLogService); + TbResource resource = new TbResource(); resource.setResourceType(ResourceType.JKS); resource.setTitle("My first resource"); @@ -83,6 +89,10 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes TbResource savedResource = save(resource); + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedResource, savedResource.getId(), savedResource.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED); + Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource.getId()); Assert.assertTrue(savedResource.getCreatedTime() > 0); @@ -98,6 +108,10 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes TbResource foundResource = doGet("/api/resource/" + savedResource.getId().getId().toString(), TbResource.class); Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(foundResource, foundResource.getId(), foundResource.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED); } @Test @@ -107,7 +121,16 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes resource.setTitle(RandomStringUtils.randomAlphabetic(300)); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - doPost("/api/resource", resource).andExpect(statusReason(containsString("length of title must be equal or less than 255"))); + + Mockito.reset(tbClusterService, auditLogService); + + String msgError = msgErrorFieldLength("title"); + doPost("/api/resource", resource) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(resource, savedTenant.getId(), + tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -118,10 +141,24 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResource savedResource = save(resource); + TbResource savedResource = save(resource); loginDifferentTenant(); - doPostWithTypedResponse("/api/resource", savedResource, new TypeReference<>(){}, status().isForbidden()); + + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/resource", savedResource) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedResource.getId(), savedResource); + + doDelete("/api/resource/" + savedResource.getId().getId().toString()) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedResource.getId(), savedResource); + deleteDifferentTenant(); } @@ -150,17 +187,29 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes TbResource savedResource = save(resource); - doDelete("/api/resource/" + savedResource.getId().getId().toString()) + Mockito.reset(tbClusterService, auditLogService); + String resourceIdStr = savedResource.getId().getId().toString(); + doDelete("/api/resource/" + resourceIdStr) .andExpect(status().isOk()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(savedResource, savedResource.getId(), savedResource.getId(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, resourceIdStr); + doGet("/api/resource/" + savedResource.getId().getId().toString()) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Resource", resourceIdStr)))); } @Test public void testFindTenantTbResources() throws Exception { + + Mockito.reset(tbClusterService, auditLogService); + List resources = new ArrayList<>(); - for (int i = 0; i < 173; i++) { + int cntEntity = 173; + for (int i = 0; i < cntEntity; i++) { TbResource resource = new TbResource(); resource.setTitle("Resource" + i); resource.setResourceType(ResourceType.JKS); @@ -173,7 +222,7 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes PageData pageData; do { pageData = doGetTypedWithPageLink("/api/resource?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedResources.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -181,6 +230,10 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes } } while (pageData.hasNext()); + testNotifyManyEntityManyTimeMsgToEdgeServiceNever(new TbResource(), new TbResource(), + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, cntEntity); + Collections.sort(resources, idComparator); Collections.sort(loadedResources, idComparator); @@ -205,7 +258,7 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes PageData pageData; do { pageData = doGetTypedWithPageLink("/api/resource?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedResources.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -218,16 +271,23 @@ public abstract class BaseTbResourceControllerTest extends AbstractControllerTes Assert.assertEquals(resources, loadedResources); + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = resources.size(); for (TbResourceInfo resource : resources) { doDelete("/api/resource/" + resource.getId().getId().toString()) .andExpect(status().isOk()); } + testNotifyManyEntityManyTimeMsgToEdgeServiceNeverAdditionalInfoAny(new TbResource(), new TbResource(), + resources.get(0).getTenantId(), null, null, SYS_ADMIN_EMAIL, + ActionType.DELETED, cntEntity, 1); + pageLink = new PageLink(27); loadedResources.clear(); do { pageData = doGetTypedWithPageLink("/api/resource?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedResources.addAll(pageData.getData()); if (pageData.hasNext()) { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java index 2cc8bccc42..1840f1f8a0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java @@ -26,6 +26,8 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; import org.springframework.test.context.TestPropertySource; import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.common.util.ThingsBoardExecutors; @@ -33,8 +35,10 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.queue.ProcessingStrategy; import org.thingsboard.server.common.data.queue.ProcessingStrategyType; import org.thingsboard.server.common.data.queue.Queue; @@ -56,6 +60,8 @@ import java.util.stream.Collectors; import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @TestPropertySource(properties = { @@ -86,17 +92,28 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { loginSysAdmin(); Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); + + Mockito.reset(tbClusterService); + Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant.getId()); Assert.assertTrue(savedTenant.getCreatedTime() > 0); Assert.assertEquals(tenant.getTitle(), savedTenant.getTitle()); + + testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.CREATED, 1); + savedTenant.setTitle("My new tenant"); doPost("/api/tenant", savedTenant, Tenant.class); Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class); Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle()); + + testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.UPDATED, 1); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) .andExpect(status().isOk()); + + testBroadcastEntityStateChangeEventTimeManyTimeTenant(savedTenant, ComponentLifecycleEvent.DELETED, 1); } @Test @@ -104,7 +121,14 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { loginSysAdmin(); Tenant tenant = new Tenant(); tenant.setTitle(RandomStringUtils.randomAlphanumeric(300)); - doPost("/api/tenant", tenant).andExpect(statusReason(containsString("length of title must be equal or less than 255"))); + + Mockito.reset(tbClusterService); + + doPost("/api/tenant", tenant) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgErrorFieldLength("title")))); + + testBroadcastEntityStateChangeEventNeverTenant(); } @Test @@ -136,21 +160,31 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { @Test public void testSaveTenantWithEmptyTitle() throws Exception { loginSysAdmin(); + + Mockito.reset(tbClusterService); + Tenant tenant = new Tenant(); doPost("/api/tenant", tenant) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Tenant title should be specified"))); + .andExpect(statusReason(containsString("Tenant title " + msgErrorShouldBeSpecified))); + + testBroadcastEntityStateChangeEventNeverTenant(); } @Test public void testSaveTenantWithInvalidEmail() throws Exception { loginSysAdmin(); + + Mockito.reset(tbClusterService); + Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); tenant.setEmail("invalid@mail"); doPost("/api/tenant", tenant) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Invalid email address format"))); + + testBroadcastEntityStateChangeEventNeverTenant(); } @Test @@ -159,10 +193,13 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + + String tenantIdStr = savedTenant.getId().getId().toString(); + doDelete("/api/tenant/" + tenantIdStr) .andExpect(status().isOk()); - doGet("/api/tenant/" + savedTenant.getId().getId().toString()) - .andExpect(status().isNotFound()); + doGet("/api/tenant/" + tenantIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Tenant", tenantIdStr)))); } @Test @@ -175,8 +212,11 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { Assert.assertEquals(1, pageData.getData().size()); tenants.addAll(pageData.getData()); + Mockito.reset(tbClusterService); + + int cntEntity = 56; List> createFutures = new ArrayList<>(56); - for (int i = 0; i < 56; i++) { + for (int i = 0; i < cntEntity; i++) { Tenant tenant = new Tenant(); tenant.setTitle("Tenant" + i); createFutures.add(executor.submit(() -> @@ -184,6 +224,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { } tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS)); + testBroadcastEntityStateChangeEventTimeManyTimeTenant(new Tenant(), ComponentLifecycleEvent.CREATED, cntEntity); + List loadedTenants = new ArrayList<>(); pageLink = new PageLink(17); do { @@ -200,6 +242,8 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { .filter((t) -> !TEST_TENANT_NAME.equals(t.getTitle())) .collect(Collectors.toList()), executor).get(TIMEOUT, TimeUnit.SECONDS); + testBroadcastEntityStateChangeEventTimeManyTimeTenant(new Tenant(), ComponentLifecycleEvent.DELETED, cntEntity); + pageLink = new PageLink(17); pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink); Assert.assertFalse(pageData.hasNext()); @@ -464,7 +508,9 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { login(username, password); for (Queue queue : foundTenantQueues) { - doGet("/api/queues/" + queue.getId()).andExpect(status().isNotFound()); + doGet("/api/queues/" + queue.getId()) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNotFound))); } loginSysAdmin(); @@ -476,7 +522,7 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { queueConfiguration.setName(queueName); queueConfiguration.setTopic("tb_rule_engine." + queueName.toLowerCase()); queueConfiguration.setPollInterval(25); - queueConfiguration.setPartitions(new Random().nextInt(100)); + queueConfiguration.setPartitions(1 + new Random().nextInt(99)); queueConfiguration.setConsumerPerPartition(true); queueConfiguration.setPackProcessingTimeout(2000); SubmitStrategy submitStrategy = new SubmitStrategy(); @@ -519,4 +565,26 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest { } return result; } + + private void testBroadcastEntityStateChangeEventTimeManyTimeTenant(Tenant tenant, ComponentLifecycleEvent event, int cntTime) { + ArgumentMatcher matcherTenant = cntTime == 1 ? argument -> argument.equals(tenant) : + argument -> argument.getClass().equals(Tenant.class); + if (ComponentLifecycleEvent.DELETED.equals(event)) { + Mockito.verify(tbClusterService, times( cntTime)).onTenantDelete(Mockito.argThat(matcherTenant), + Mockito.isNull()); + } else { + Mockito.verify(tbClusterService, times( cntTime)).onTenantChange(Mockito.argThat(matcherTenant), + Mockito.isNull()); + } + TenantId tenantId = cntTime == 1 ? tenant.getId() : (TenantId) createEntityId_NULL_UUID(tenant); + testBroadcastEntityStateChangeEventTime(tenantId, tenantId, cntTime); + Mockito.reset(tbClusterService); + } + + private void testBroadcastEntityStateChangeEventNeverTenant() { + Mockito.verify(tbClusterService, never()).onTenantChange(Mockito.any(Tenant.class), + Mockito.isNull()); + testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant())); + Mockito.reset(tbClusterService); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java index 1867577c23..11174618ed 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java @@ -17,24 +17,24 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.lang3.RandomStringUtils; -import org.junit.After; import org.junit.Assert; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; +import org.mockito.ArgumentMatcher; +import org.mockito.Mockito; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.queue.ProcessingStrategy; import org.thingsboard.server.common.data.queue.ProcessingStrategyType; import org.thingsboard.server.common.data.queue.SubmitStrategy; import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; -import org.thingsboard.server.dao.tenant.TenantProfileService; import java.util.ArrayList; import java.util.Collections; @@ -42,6 +42,8 @@ import java.util.List; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseTenantProfileControllerTest extends AbstractControllerTest { @@ -52,6 +54,9 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController @Test public void testSaveTenantProfile() throws Exception { loginSysAdmin(); + + Mockito.reset(tbClusterService); + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"); TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); Assert.assertNotNull(savedTenantProfile); @@ -61,20 +66,30 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController Assert.assertEquals(tenantProfile.getDescription(), savedTenantProfile.getDescription()); Assert.assertEquals(tenantProfile.getProfileData(), savedTenantProfile.getProfileData()); Assert.assertEquals(tenantProfile.isDefault(), savedTenantProfile.isDefault()); - Assert.assertEquals(tenantProfile.isIsolatedTbCore(), savedTenantProfile.isIsolatedTbCore()); Assert.assertEquals(tenantProfile.isIsolatedTbRuleEngine(), savedTenantProfile.isIsolatedTbRuleEngine()); + testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.CREATED, 1); + savedTenantProfile.setName("New tenant profile"); doPost("/api/tenantProfile", savedTenantProfile, TenantProfile.class); TenantProfile foundTenantProfile = doGet("/api/tenantProfile/"+savedTenantProfile.getId().getId().toString(), TenantProfile.class); Assert.assertEquals(foundTenantProfile.getName(), savedTenantProfile.getName()); + + testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.UPDATED, 1); } @Test public void testSaveTenantProfileWithViolationOfLengthValidation() throws Exception { loginSysAdmin(); + + Mockito.reset(tbClusterService); + TenantProfile tenantProfile = this.createTenantProfile(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/tenantProfile", tenantProfile).andExpect(statusReason(containsString("length of name must be equal or less than 255"))); + doPost("/api/tenantProfile", tenantProfile) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgErrorFieldLength("name")))); + + testBroadcastEntityStateChangeEventNeverTenantProfile(); } @Test @@ -122,9 +137,15 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController @Test public void testSaveTenantProfileWithEmptyName() throws Exception { loginSysAdmin(); + + Mockito.reset(tbClusterService); + TenantProfile tenantProfile = new TenantProfile(); doPost("/api/tenantProfile", tenantProfile).andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Tenant profile name should be specified"))); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Tenant profile name " + msgErrorShouldBeSpecified))); + + testBroadcastEntityStateChangeEventNeverTenantProfile(); } @Test @@ -132,9 +153,15 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController loginSysAdmin(); TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"); doPost("/api/tenantProfile", tenantProfile).andExpect(status().isOk()); + + Mockito.reset(tbClusterService); + TenantProfile tenantProfile2 = this.createTenantProfile("Tenant Profile"); - doPost("/api/tenantProfile", tenantProfile2).andExpect(status().isBadRequest()) + doPost("/api/tenantProfile", tenantProfile2) + .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Tenant profile with such name already exists"))); + + testBroadcastEntityStateChangeEventNeverTenantProfile(); } @Test @@ -144,18 +171,14 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); savedTenantProfile.setIsolatedTbRuleEngine(true); addMainQueueConfig(savedTenantProfile); - doPost("/api/tenantProfile", savedTenantProfile).andExpect(status().isBadRequest()) + + Mockito.reset(tbClusterService); + + doPost("/api/tenantProfile", savedTenantProfile) + .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Can't update isolatedTbRuleEngine property"))); - } - @Test - public void testSaveSameTenantProfileWithDifferentIsolatedTbCore() throws Exception { - loginSysAdmin(); - TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"); - TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); - savedTenantProfile.setIsolatedTbCore(true); - doPost("/api/tenantProfile", savedTenantProfile).andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Can't update isolatedTbCore property"))); + testBroadcastEntityStateChangeEventNeverTenantProfile(); } @Test @@ -169,10 +192,14 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController tenant.setTenantProfileId(savedTenantProfile.getId()); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Mockito.reset(tbClusterService); + doDelete("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString()) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("The tenant profile referenced by the tenants cannot be deleted"))); + testBroadcastEntityStateChangeEventNeverTenantProfile(); + doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) .andExpect(status().isOk()); } @@ -183,11 +210,16 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"); TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + Mockito.reset(tbClusterService); + doDelete("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString()) .andExpect(status().isOk()); + testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(savedTenantProfile, ComponentLifecycleEvent.DELETED, 1); + doGet("/api/tenantProfile/" + savedTenantProfile.getId().getId().toString()) - .andExpect(status().isNotFound()); + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Tenant profile", savedTenantProfile.getId().getId().toString())))); } @Test @@ -196,21 +228,26 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController List tenantProfiles = new ArrayList<>(); PageLink pageLink = new PageLink(17); PageData pageData = doGetTypedWithPageLink("/api/tenantProfiles?", - new TypeReference>(){}, pageLink); + new TypeReference<>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); tenantProfiles.addAll(pageData.getData()); + Mockito.reset(tbClusterService); + + int cntEntity = 28; for (int i=0;i<28;i++) { TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i); tenantProfiles.add(doPost("/api/tenantProfile", tenantProfile, TenantProfile.class)); } + testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(new TenantProfile(), ComponentLifecycleEvent.CREATED, cntEntity); + List loadedTenantProfiles = new ArrayList<>(); pageLink = new PageLink(17); do { pageData = doGetTypedWithPageLink("/api/tenantProfiles?", - new TypeReference>(){}, pageLink); + new TypeReference<>(){}, pageLink); loadedTenantProfiles.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -222,6 +259,8 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController Assert.assertEquals(tenantProfiles, loadedTenantProfiles); + Mockito.reset(tbClusterService); + for (TenantProfile tenantProfile : loadedTenantProfiles) { if (!tenantProfile.isDefault()) { doDelete("/api/tenantProfile/" + tenantProfile.getId().getId().toString()) @@ -234,6 +273,8 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController new TypeReference>(){}, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); + + testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(new TenantProfile(), ComponentLifecycleEvent.DELETED, cntEntity); } @Test @@ -242,7 +283,7 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController List tenantProfiles = new ArrayList<>(); PageLink pageLink = new PageLink(17); PageData tenantProfilePageData = doGetTypedWithPageLink("/api/tenantProfiles?", - new TypeReference>(){}, pageLink); + new TypeReference<>(){}, pageLink); Assert.assertFalse(tenantProfilePageData.hasNext()); Assert.assertEquals(1, tenantProfilePageData.getTotalElements()); tenantProfiles.addAll(tenantProfilePageData.getData()); @@ -294,7 +335,6 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); tenantProfile.setProfileData(tenantProfileData); tenantProfile.setDefault(false); - tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbRuleEngine(false); return tenantProfile; } @@ -322,4 +362,28 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController profileData.setQueueConfiguration(Collections.singletonList(mainQueueConfiguration)); tenantProfile.setProfileData(profileData); } + + + private void testBroadcastEntityStateChangeEventTimeManyTimeTenantProfile(TenantProfile tenantProfile, ComponentLifecycleEvent event, int cntTime) { + ArgumentMatcher matcherTenantProfile = cntTime == 1 ? argument -> argument.equals(tenantProfile) : + argument -> argument.getClass().equals(TenantProfile.class); + if (ComponentLifecycleEvent.DELETED.equals(event)) { + Mockito.verify(tbClusterService, times( cntTime)).onTenantProfileDelete(Mockito.argThat( matcherTenantProfile), + Mockito.isNull()); + testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant())); + } else { + Mockito.verify(tbClusterService, times( cntTime)).onTenantProfileChange(Mockito.argThat(matcherTenantProfile), + Mockito.isNull()); + TenantProfileId tenantProfileIdId = cntTime == 1 ? tenantProfile.getId() : (TenantProfileId) createEntityId_NULL_UUID(tenantProfile); + testBroadcastEntityStateChangeEventTime(tenantProfileIdId, null, cntTime); + } + Mockito.reset(tbClusterService); + } + + private void testBroadcastEntityStateChangeEventNeverTenantProfile() { + Mockito.verify(tbClusterService, never()).onTenantProfileChange(Mockito.any(TenantProfile.class), + Mockito.isNull()); + testBroadcastEntityStateChangeEventNever(createEntityId_NULL_UUID(new Tenant())); + Mockito.reset(tbClusterService, auditLogService); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 5b917ea8fa..6e8c15cc0e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -21,15 +21,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; import org.springframework.http.HttpHeaders; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.service.mail.TestMailService; import java.util.ArrayList; @@ -42,11 +45,14 @@ import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.dao.model.ModelConstants.SYSTEM_TENANT; public abstract class BaseUserControllerTest extends AbstractControllerTest { private IdComparator idComparator = new IdComparator<>(); + private CustomerId customerNUULId = (CustomerId) createEntityId_NULL_UUID(new Customer()); + @Test public void testSaveUser() throws Exception { loginSysAdmin(); @@ -58,6 +64,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setEmail(email); user.setFirstName("Joe"); user.setLastName("Downs"); + + Mockito.reset(tbClusterService, auditLogService); + User savedUser = doPost("/api/user", user, User.class); Assert.assertNotNull(savedUser); Assert.assertNotNull(savedUser.getId()); @@ -67,6 +76,11 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); Assert.assertEquals(foundUser, savedUser); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundUser, foundUser, + SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, ActionType.ADDED, 1, 1, 1); + Mockito.reset(tbClusterService, auditLogService); + logout(); doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) .andExpect(status().isSeeOther()) @@ -94,14 +108,24 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(jsonPath("$.email", is(email))); loginSysAdmin(); + foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); + + Mockito.reset(tbClusterService, auditLogService); + doDelete("/api/user/" + savedUser.getId().getId().toString()) .andExpect(status().isOk()); + + testNotifyEntityOneTimeMsgToEdgeServiceNever(foundUser, foundUser.getId(), foundUser.getId(), + SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, + ActionType.DELETED, foundUser.getId().getId().toString()); } @Test public void testSaveUserWithViolationOfFiledValidation() throws Exception { loginSysAdmin(); + Mockito.reset(tbClusterService, auditLogService); + String email = "tenant2@thingsboard.org"; User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); @@ -109,10 +133,26 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setEmail(email); user.setFirstName(RandomStringUtils.randomAlphabetic(300)); user.setLastName("Downs"); - doPost("/api/user", user).andExpect(statusReason(containsString("Validation error: length of first name must be equal or less than 255"))); + String msgError = msgErrorFieldLength("first name"); + doPost("/api/user", user) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(user, + SYSTEM_TENANT, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); + Mockito.reset(tbClusterService, auditLogService); + user.setFirstName("Normal name"); + msgError = msgErrorFieldLength("last name"); user.setLastName(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/user", user).andExpect(statusReason(containsString("length of last name must be equal or less than 255"))); + doPost("/api/user", user) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(user, + SYSTEM_TENANT, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); } @Test @@ -128,9 +168,16 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); loginDifferentTenant(); - doPost("/api/user", tenantAdmin, User.class, status().isForbidden()); - deleteDifferentTenant(); + Mockito.reset(tbClusterService, auditLogService); + + doPost("/api/user", tenantAdmin) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(tenantAdmin.getId(), tenantAdmin); + + deleteDifferentTenant(); } @Test @@ -162,7 +209,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .put("resetToken", TestMailService.currentResetPasswordToken) .put("password", "testPassword2"); - JsonNode tokenInfo = readResponse(doPost("/api/noauth/resetPassword", resetPasswordRequest).andExpect(status().isOk()), JsonNode.class); + JsonNode tokenInfo = readResponse( + doPost("/api/noauth/resetPassword", resetPasswordRequest) + .andExpect(status().isOk()), JsonNode.class); validateAndSetJwtToken(tokenInfo, email); doGet("/api/auth/user") @@ -205,6 +254,8 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { public void testSaveUserWithSameEmail() throws Exception { loginSysAdmin(); + Mockito.reset(tbClusterService, auditLogService); + String email = TENANT_ADMIN_EMAIL; User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); @@ -213,15 +264,22 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setFirstName("Joe"); user.setLastName("Downs"); + String msgError = "User with email '" + email + "' already present in database"; doPost("/api/user", user) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("User with email '" + email + "' already present in database"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(user, + SYSTEM_TENANT, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testSaveUserWithInvalidEmail() throws Exception { loginSysAdmin(); + Mockito.reset(tbClusterService, auditLogService); + String email = "tenant_thingsboard.org"; User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); @@ -230,39 +288,59 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setFirstName("Joe"); user.setLastName("Downs"); + String msgError = "Invalid email address format '" + email + "'"; doPost("/api/user", user) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Invalid email address format '" + email + "'"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(user, + SYSTEM_TENANT, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testSaveUserWithEmptyEmail() throws Exception { loginSysAdmin(); + Mockito.reset(tbClusterService, auditLogService); + User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); user.setFirstName("Joe"); user.setLastName("Downs"); + String msgError = "User email " + msgErrorShouldBeSpecified; doPost("/api/user", user) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("User email should be specified"))); + .andExpect(statusReason(containsString("User email " + msgErrorShouldBeSpecified))); + + testNotifyEntityEqualsOneTimeServiceNeverError(user, + SYSTEM_TENANT, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); } @Test public void testSaveUserWithoutTenant() throws Exception { loginSysAdmin(); + Mockito.reset(tbClusterService, auditLogService); + User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setEmail("tenant2@thingsboard.org"); user.setFirstName("Joe"); user.setLastName("Downs"); + String msgError = "Tenant administrator should be assigned to tenant"; doPost("/api/user", user) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Tenant administrator should be assigned to tenant"))); + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityEqualsOneTimeServiceNeverError(user, + SYSTEM_TENANT, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, new DataValidationException(msgError)); + } @Test @@ -284,8 +362,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { doDelete("/api/user/" + savedUser.getId().getId().toString()) .andExpect(status().isOk()); - doGet("/api/user/" + savedUser.getId().getId().toString()) - .andExpect(status().isNotFound()); + String userIdStr = savedUser.getId().getId().toString(); + doGet("/api/user/" + userIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString( msgErrorNoFound("User",userIdStr)))); } @Test @@ -300,8 +380,11 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { TenantId tenantId = savedTenant.getId(); + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = 64; List tenantAdmins = new ArrayList<>(); - for (int i = 0; i < 64; i++) { + for (int i = 0; i < cntEntity; i++) { User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); @@ -309,12 +392,18 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { tenantAdmins.add(doPost("/api/user", user, User.class)); } + User testManyUser = new User(); + testManyUser.setTenantId(tenantId); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(testManyUser, testManyUser, + SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, + ActionType.ADDED, ActionType.ADDED, cntEntity, cntEntity, cntEntity); + List loadedTenantAdmins = new ArrayList<>(); PageLink pageLink = new PageLink(33); PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedTenantAdmins.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -333,7 +422,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { pageLink = new PageLink(33); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); @@ -377,7 +466,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedTenantAdminsEmail1.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -394,7 +483,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { pageLink = new PageLink(16, 0, email2); do { pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedTenantAdminsEmail2.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -407,14 +496,22 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { Assert.assertEquals(tenantAdminsEmail2, loadedTenantAdminsEmail2); + Mockito.reset(tbClusterService, auditLogService); + + int cntEntity = loadedTenantAdminsEmail1.size(); for (User user : loadedTenantAdminsEmail1) { doDelete("/api/user/" + user.getId().getId().toString()) .andExpect(status().isOk()); } + User testManyUser = new User(); + testManyUser.setTenantId(tenantId); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(testManyUser, testManyUser, + SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, + ActionType.DELETED, ActionType.DELETED, cntEntity, 0, cntEntity, new String()); pageLink = new PageLink(4, 0, email1); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -426,7 +523,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, email2); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -443,7 +540,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { tenantAdmin.setFirstName("Joe"); tenantAdmin.setLastName("Downs"); - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + createUserAndLogin(tenantAdmin, "testPassword1"); Customer customer = new Customer(); customer.setTitle("My customer"); @@ -465,7 +562,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { PageData pageData = null; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedCustomerUsers.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -493,7 +590,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { tenantAdmin.setFirstName("Joe"); tenantAdmin.setLastName("Downs"); - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + createUserAndLogin(tenantAdmin, "testPassword1"); Customer customer = new Customer(); customer.setTitle("My customer"); @@ -531,10 +628,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { List loadedCustomerUsersEmail1 = new ArrayList<>(); PageLink pageLink = new PageLink(33, 0, email1); - PageData pageData = null; + PageData pageData; do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedCustomerUsersEmail1.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -551,7 +648,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { pageLink = new PageLink(16, 0, email2); do { pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); loadedCustomerUsersEmail2.addAll(pageData.getData()); if (pageData.hasNext()) { @@ -571,7 +668,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, email1); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -583,7 +680,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { pageLink = new PageLink(4, 0, email2); pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", - new TypeReference>() { + new TypeReference<>() { }, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); @@ -591,5 +688,4 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { doDelete("/api/customer/" + customerId.getId().toString()) .andExpect(status().isOk()); } - } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java index 21acf5ee81..68ed3636e9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java @@ -21,8 +21,12 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; +import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -34,6 +38,7 @@ import java.util.List; import static org.hamcrest.Matchers.containsString; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.dao.model.ModelConstants.SYSTEM_TENANT; public abstract class BaseWidgetsBundleControllerTest extends AbstractControllerTest { @@ -73,8 +78,16 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController public void testSaveWidgetsBundle() throws Exception { WidgetsBundle widgetsBundle = new WidgetsBundle(); widgetsBundle.setTitle("My widgets bundle"); + + Mockito.reset(tbClusterService); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedWidgetsBundle, savedWidgetsBundle, + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.ADDED, ActionType.ADDED, 0, 1, 0); + Mockito.reset(tbClusterService); + Assert.assertNotNull(savedWidgetsBundle); Assert.assertNotNull(savedWidgetsBundle.getId()); Assert.assertNotNull(savedWidgetsBundle.getAlias()); @@ -87,13 +100,25 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController WidgetsBundle foundWidgetsBundle = doGet("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString(), WidgetsBundle.class); Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedWidgetsBundle, savedWidgetsBundle, + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.UPDATED, ActionType.UPDATED, 0, 1, 0); } @Test public void testSaveWidgetBundleWithViolationOfLengthValidation() throws Exception { WidgetsBundle widgetsBundle = new WidgetsBundle(); widgetsBundle.setTitle(RandomStringUtils.randomAlphabetic(300)); - doPost("/api/widgetsBundle", widgetsBundle).andExpect(statusReason(containsString("length of title must be equal or less than 255"))); + + Mockito.reset(tbClusterService); + + String msgError = msgErrorFieldLength("title"); + doPost("/api/widgetsBundle", widgetsBundle) + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString(msgError))); + + testNotifyEntityNever(widgetsBundle.getId(), widgetsBundle); } @Test @@ -103,7 +128,15 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); loginDifferentTenant(); - doPost("/api/widgetsBundle", savedWidgetsBundle, WidgetsBundle.class, status().isForbidden()); + + Mockito.reset(tbClusterService); + + doPost("/api/widgetsBundle", savedWidgetsBundle) + .andExpect(status().isForbidden()) + .andExpect(statusReason(containsString(msgErrorPermission))); + + testNotifyEntityNever(savedWidgetsBundle.getId(), savedWidgetsBundle); + deleteDifferentTenant(); } @@ -121,21 +154,35 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController public void testDeleteWidgetsBundle() throws Exception { WidgetsBundle widgetsBundle = new WidgetsBundle(); widgetsBundle.setTitle("My widgets bundle"); + + Mockito.reset(tbClusterService, auditLogService); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); doDelete("/api/widgetsBundle/"+savedWidgetsBundle.getId().getId().toString()) .andExpect(status().isOk()); - doGet("/api/widgetsBundle/"+savedWidgetsBundle.getId().getId().toString()) - .andExpect(status().isNotFound()); + String savedWidgetsBundleIdStr = savedWidgetsBundle.getId().getId().toString(); + doGet("/api/widgetsBundle/" + savedWidgetsBundleIdStr) + .andExpect(status().isNotFound()) + .andExpect(statusReason(containsString(msgErrorNoFound("Widgets bundle", savedWidgetsBundleIdStr)))); + + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(savedWidgetsBundle, savedWidgetsBundle, + savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), + ActionType.DELETED, ActionType.DELETED, 0, 1, 0); } @Test public void testSaveWidgetsBundleWithEmptyTitle() throws Exception { + + Mockito.reset(tbClusterService, auditLogService); + WidgetsBundle widgetsBundle = new WidgetsBundle(); doPost("/api/widgetsBundle", widgetsBundle) .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Widgets bundle title should be specified"))); + .andExpect(statusReason(containsString("Widgets bundle title " + msgErrorShouldBeSpecified))); + + testNotifyEntityNever(widgetsBundle.getId(), widgetsBundle); } @Test @@ -144,10 +191,14 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController widgetsBundle.setTitle("My widgets bundle"); WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); savedWidgetsBundle.setAlias("new_alias"); + + Mockito.reset(tbClusterService); + doPost("/api/widgetsBundle", savedWidgetsBundle) .andExpect(status().isBadRequest()) .andExpect(statusReason(containsString("Update of widgets bundle alias is prohibited"))); + testNotifyEntityNever(savedWidgetsBundle.getId(), savedWidgetsBundle); } @Test @@ -156,16 +207,22 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController login(tenantAdmin.getEmail(), "testPassword1"); List sysWidgetsBundles = doGetTyped("/api/widgetsBundles?", - new TypeReference>(){}); + new TypeReference<>(){}); + Mockito.reset(tbClusterService); + int cntEntity = 73; List widgetsBundles = new ArrayList<>(); - for (int i=0;i<73;i++) { + for (int i=0;i loadedWidgetsBundles = new ArrayList<>(); @@ -173,7 +230,7 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController PageData pageData; do { pageData = doGetTypedWithPageLink("/api/widgetsBundles?", - new TypeReference>(){}, pageLink); + new TypeReference<>(){}, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -192,10 +249,11 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController loginSysAdmin(); List sysWidgetsBundles = doGetTyped("/api/widgetsBundles?", - new TypeReference>(){}); + new TypeReference<>(){}); + int cntEntity = 120; List createdWidgetsBundles = new ArrayList<>(); - for (int i=0;i<120;i++) { + for (int i=0;i pageData; do { pageData = doGetTypedWithPageLink("/api/widgetsBundles?", - new TypeReference>(){}, pageLink); + new TypeReference<>(){}, pageLink); loadedWidgetsBundles.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -221,11 +279,17 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController Assert.assertEquals(widgetsBundles, loadedWidgetsBundles); + Mockito.reset(tbClusterService); + for (WidgetsBundle widgetsBundle : createdWidgetsBundles) { doDelete("/api/widgetsBundle/"+widgetsBundle.getId().getId().toString()) .andExpect(status().isOk()); } + testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(new WidgetsBundle(), new WidgetsBundle(), + SYSTEM_TENANT, (CustomerId) createEntityId_NULL_UUID(new Customer()), null, SYS_ADMIN_EMAIL, + ActionType.DELETED, ActionType.DELETED, 0, cntEntity, 0); + pageLink = new PageLink(17); loadedWidgetsBundles.clear(); do { @@ -262,7 +326,7 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController widgetsBundles.addAll(sysWidgetsBundles); List loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?", - new TypeReference>(){}); + new TypeReference<>(){}); Collections.sort(widgetsBundles, idComparator); Collections.sort(loadedWidgetsBundles, idComparator); @@ -277,7 +341,7 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController List sysWidgetsBundles = doGetTyped("/api/widgetsBundles?", - new TypeReference>(){}); + new TypeReference<>(){}); List createdSystemWidgetsBundles = new ArrayList<>(); for (int i=0;i<82;i++) { @@ -324,7 +388,7 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController } loadedWidgetsBundles = doGetTyped("/api/widgetsBundles?", - new TypeReference>(){}); + new TypeReference<>(){}); Collections.sort(sysWidgetsBundles, idComparator); Collections.sort(loadedWidgetsBundles, idComparator); diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.java new file mode 100644 index 0000000000..44f4db1fd1 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/sql/EntityRelationControllerSqlTest.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.BaseEntityRelationControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class EntityRelationControllerSqlTest extends BaseEntityRelationControllerTest { +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 61bb53b060..4bcb9c6d59 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.gson.JsonObject; import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.MessageLite; import org.apache.commons.lang3.RandomStringUtils; @@ -32,7 +33,10 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; @@ -42,6 +46,8 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; @@ -68,13 +74,21 @@ import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.EntityKeyValueType; import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.NumericFilterPredicate; +import org.thingsboard.server.common.data.queue.ProcessingStrategy; +import org.thingsboard.server.common.data.queue.ProcessingStrategyType; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.queue.SubmitStrategy; +import org.thingsboard.server.common.data.queue.SubmitStrategyType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; @@ -87,6 +101,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.edge.EdgeEventService; @@ -107,6 +122,8 @@ import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg; +import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.RpcResponseMsg; @@ -123,6 +140,7 @@ import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.transport.TransportProtos; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -133,18 +151,21 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; @TestPropertySource(properties = { "edges.enabled=true", }) abstract public class BaseEdgeTest extends AbstractControllerTest { - private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat"; + private static final String THERMOSTAT_DEVICE_PROFILE_NAME = "Thermostat"; private Tenant savedTenant; private TenantId tenantId; private User tenantAdmin; + private DeviceProfile thermostatDeviceProfile; + private EdgeImitator edgeImitator; private Edge edge; @@ -180,12 +201,19 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { installation(); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); - edgeImitator.expectMessageAmount(13); + edgeImitator.expectMessageAmount(14); edgeImitator.connect(); verifyEdgeConnectionAndInitialData(); } + private QueueId getRandomQueueId() throws Exception { + List ruleEngineQueues = doGetTypedWithPageLink("/api/queues?serviceType={serviceType}&", + new TypeReference>() {}, new PageLink(100), ServiceType.TB_RULE_ENGINE.name()) + .getData(); + return ruleEngineQueues.get(0).getId(); + } + @After public void afterTest() throws Exception { try { @@ -204,12 +232,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration(); transportConfiguration.setTransportPayloadTypeConfiguration(new JsonTransportPayloadConfiguration()); - DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, transportConfiguration); + thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, transportConfiguration); - extendDeviceProfileData(deviceProfile); - doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + extendDeviceProfileData(thermostatDeviceProfile); + thermostatDeviceProfile = doPost("/api/deviceProfile", thermostatDeviceProfile, DeviceProfile.class); - Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME); + Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME); doPost("/api/edge/" + edge.getUuidId() + "/device/" + savedDevice.getUuidId(), Device.class); @@ -269,7 +297,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { List deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class); Assert.assertEquals(3, deviceProfileUpdateMsgList.size()); Optional deviceProfileUpdateMsgOpt = - deviceProfileUpdateMsgList.stream().filter(dfum -> CUSTOM_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); + deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent()); DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); @@ -1339,17 +1367,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { String timeseriesKey = "key"; String timeseriesValue = "25"; data.addProperty(timeseriesKey, timeseriesValue); - UplinkMsg.Builder uplinkMsgBuilder1 = UplinkMsg.newBuilder(); + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); EntityDataProto.Builder entityDataBuilder = EntityDataProto.newBuilder(); entityDataBuilder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data, System.currentTimeMillis())); entityDataBuilder.setEntityType(device.getId().getEntityType().name()); entityDataBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits()); entityDataBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits()); testAutoGeneratedCodeByProtobuf(entityDataBuilder); - uplinkMsgBuilder1.addEntityData(entityDataBuilder.build()); + uplinkMsgBuilder.addEntityData(entityDataBuilder.build()); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder1.build()); + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); JsonObject attributesData = new JsonObject(); String attributesKey = "test_attr"; @@ -1381,9 +1409,24 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE; List> attributes = doGetAsyncTyped(attributeValuesUrl, new TypeReference<>() {}); - Assert.assertEquals(2, attributes.size()); - var result = attributes.stream().filter(kv -> kv.get("key").equals(attributesKey)).filter(kv -> kv.get("value").equals(attributesValue)).findFirst(); - Assert.assertTrue(result.isPresent()); + + Assert.assertEquals(3, attributes.size()); + + Optional> activeAttributeOpt = getAttributeByKey("active", attributes); + Assert.assertTrue(activeAttributeOpt.isPresent()); + Map activeAttribute = activeAttributeOpt.get(); + Assert.assertEquals("true", activeAttribute.get("value")); + + Optional> customAttributeOpt = getAttributeByKey(attributesKey, attributes); + Assert.assertTrue(customAttributeOpt.isPresent()); + Map customAttribute = customAttributeOpt.get(); + Assert.assertEquals(attributesValue, customAttribute.get("value")); + + doDelete("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/SERVER_SCOPE?keys=" + attributesKey, String.class); + } + + private Optional> getAttributeByKey(String key, List> attributes) { + return attributes.stream().filter(kv -> kv.get("key").equals(key)).findFirst(); } private Map>> loadDeviceTimeseries(Device device, String timeseriesKey) throws Exception { @@ -1643,6 +1686,183 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertTrue("Expected key and value must be found", found); } + @Test + public void testOtaPackages_usesUrl() throws Exception { + // 1 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); + firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("My firmware #1"); + firmwareInfo.setVersion("v1.0"); + firmwareInfo.setTag("My firmware #1 v1.0"); + firmwareInfo.setUsesUrl(true); + firmwareInfo.setUrl("http://localhost:8080/v1/package"); + firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + + edgeImitator.expectMessageAmount(1); + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB()); + Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType())); + Assert.assertEquals("My firmware #1", otaPackageUpdateMsg.getTitle()); + Assert.assertEquals("v1.0", otaPackageUpdateMsg.getVersion()); + Assert.assertEquals("My firmware #1 v1.0", otaPackageUpdateMsg.getTag()); + Assert.assertEquals("http://localhost:8080/v1/package", otaPackageUpdateMsg.getUrl()); + Assert.assertFalse(otaPackageUpdateMsg.hasData()); + Assert.assertFalse(otaPackageUpdateMsg.hasFileName()); + Assert.assertFalse(otaPackageUpdateMsg.hasContentType()); + Assert.assertFalse(otaPackageUpdateMsg.hasChecksumAlgorithm()); + Assert.assertFalse(otaPackageUpdateMsg.hasChecksum()); + Assert.assertFalse(otaPackageUpdateMsg.hasDataSize()); + + // 2 + edgeImitator.expectMessageAmount(1); + doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(otaPackageUpdateMsg.getIdMSB(), savedFirmwareInfo.getUuidId().getMostSignificantBits()); + Assert.assertEquals(otaPackageUpdateMsg.getIdLSB(), savedFirmwareInfo.getUuidId().getLeastSignificantBits()); + } + + @Test + public void testOtaPackages_hasData() throws Exception { + // 1 + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); + firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("My firmware #2"); + firmwareInfo.setVersion("v2.0"); + firmwareInfo.setTag("My firmware #2 v2.0"); + firmwareInfo.setUsesUrl(false); + firmwareInfo.setHasData(false); + firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + + edgeImitator.expectMessageAmount(1); + + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + MockMultipartFile testData = new MockMultipartFile("file", "firmware.bin", "image/png", ByteBuffer.wrap(new byte[]{1, 3, 5}).array()); + savedFirmwareInfo = saveData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksumAlgorithm={checksumAlgorithm}", testData, ChecksumAlgorithm.SHA256.name()); + + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB()); + Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType())); + Assert.assertEquals("My firmware #2", otaPackageUpdateMsg.getTitle()); + Assert.assertEquals("v2.0", otaPackageUpdateMsg.getVersion()); + Assert.assertEquals("My firmware #2 v2.0", otaPackageUpdateMsg.getTag()); + Assert.assertFalse(otaPackageUpdateMsg.hasUrl()); + Assert.assertEquals("firmware.bin", otaPackageUpdateMsg.getFileName()); + Assert.assertEquals("image/png", otaPackageUpdateMsg.getContentType()); + Assert.assertEquals(ChecksumAlgorithm.SHA256.name(), otaPackageUpdateMsg.getChecksumAlgorithm()); + Assert.assertEquals("62467691cf583d4fa78b18fafaf9801f505e0ef03baf0603fd4b0cd004cd1e75", otaPackageUpdateMsg.getChecksum()); + Assert.assertEquals(3L, otaPackageUpdateMsg.getDataSize()); + Assert.assertEquals(ByteString.copyFrom(new byte[]{1, 3, 5}), otaPackageUpdateMsg.getData()); + + // 2 + edgeImitator.expectMessageAmount(1); + doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(otaPackageUpdateMsg.getIdMSB(), savedFirmwareInfo.getUuidId().getMostSignificantBits()); + Assert.assertEquals(otaPackageUpdateMsg.getIdLSB(), savedFirmwareInfo.getUuidId().getLeastSignificantBits()); + } + + @Test + public void testQueues() throws Exception { + loginSysAdmin(); + + // 1 + Queue queue = new Queue(); + queue.setName("EdgeMain"); + queue.setTopic("tb_rule_engine.EdgeMain"); + queue.setPollInterval(25); + queue.setPartitions(10); + queue.setConsumerPerPartition(false); + queue.setPackProcessingTimeout(2000); + SubmitStrategy submitStrategy = new SubmitStrategy(); + submitStrategy.setType(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR); + queue.setSubmitStrategy(submitStrategy); + ProcessingStrategy processingStrategy = new ProcessingStrategy(); + processingStrategy.setType(ProcessingStrategyType.RETRY_ALL); + processingStrategy.setRetries(3); + processingStrategy.setFailurePercentage(0.7); + processingStrategy.setPauseBetweenRetries(3); + processingStrategy.setMaxPauseBetweenRetries(5); + queue.setProcessingStrategy(processingStrategy); + + edgeImitator.expectMessageAmount(1); + Queue savedQueue = doPost("/api/queues?serviceType=" + ServiceType.TB_RULE_ENGINE.name(), queue, Queue.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); + QueueUpdateMsg queueUpdateMsg = (QueueUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); + Assert.assertEquals(savedQueue.getUuidId().getMostSignificantBits(), queueUpdateMsg.getIdMSB()); + Assert.assertEquals(savedQueue.getUuidId().getLeastSignificantBits(), queueUpdateMsg.getIdLSB()); + Assert.assertEquals(savedQueue.getTenantId().getId().getMostSignificantBits(), queueUpdateMsg.getTenantIdMSB()); + Assert.assertEquals(savedQueue.getTenantId().getId().getLeastSignificantBits(), queueUpdateMsg.getTenantIdLSB()); + Assert.assertEquals("EdgeMain", queueUpdateMsg.getName()); + Assert.assertEquals("tb_rule_engine.EdgeMain", queueUpdateMsg.getTopic()); + Assert.assertEquals(25, queueUpdateMsg.getPollInterval()); + Assert.assertEquals(10, queueUpdateMsg.getPartitions()); + Assert.assertFalse(queueUpdateMsg.getConsumerPerPartition()); + Assert.assertEquals(2000, queueUpdateMsg.getPackProcessingTimeout()); + Assert.assertEquals(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR.name(), queueUpdateMsg.getSubmitStrategy().getType()); + Assert.assertEquals(0, queueUpdateMsg.getSubmitStrategy().getBatchSize()); + Assert.assertEquals(ProcessingStrategyType.RETRY_ALL.name(), queueUpdateMsg.getProcessingStrategy().getType()); + Assert.assertEquals(3, queueUpdateMsg.getProcessingStrategy().getRetries()); + Assert.assertEquals(0.7, queueUpdateMsg.getProcessingStrategy().getFailurePercentage(), 1); + Assert.assertEquals(3, queueUpdateMsg.getProcessingStrategy().getPauseBetweenRetries()); + Assert.assertEquals(5, queueUpdateMsg.getProcessingStrategy().getMaxPauseBetweenRetries()); + + // 2 + edgeImitator.expectMessageAmount(1); + savedQueue.setPollInterval(50); + savedQueue = doPost("/api/queues?serviceType=" + ServiceType.TB_RULE_ENGINE.name(), savedQueue, Queue.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); + queueUpdateMsg = (QueueUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); + Assert.assertEquals(50, queueUpdateMsg.getPollInterval()); + + // 3 + edgeImitator.expectMessageAmount(1); + doDelete("/api/queues/" + savedQueue.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); + queueUpdateMsg = (QueueUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); + Assert.assertEquals(queueUpdateMsg.getIdMSB(), savedQueue.getUuidId().getMostSignificantBits()); + Assert.assertEquals(queueUpdateMsg.getIdLSB(), savedQueue.getUuidId().getLeastSignificantBits()); + } + // Utility methods private Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception { @@ -1723,4 +1943,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(source, target); Assert.assertEquals(source.hashCode(), target.hashCode()); } + + private OtaPackageInfo saveData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { + MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); + postRequest.file(content); + setJwtToken(postRequest); + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); + } + } diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index cfb652dd9f..1c0436d329 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -41,6 +41,8 @@ import org.thingsboard.server.gen.edge.v1.DownlinkResponseMsg; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.edge.v1.EntityDataProto; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; +import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; @@ -277,6 +279,16 @@ public class EdgeImitator { result.add(saveDownlinkMsg(deviceCredentialsRequestMsg)); } } + if (downlinkMsg.getOtaPackageUpdateMsgCount() > 0) { + for (OtaPackageUpdateMsg otaPackageUpdateMsg : downlinkMsg.getOtaPackageUpdateMsgList()) { + result.add(saveDownlinkMsg(otaPackageUpdateMsg)); + } + } + if (downlinkMsg.getQueueUpdateMsgCount() > 0) { + for (QueueUpdateMsg queueUpdateMsg : downlinkMsg.getQueueUpdateMsgList()) { + result.add(saveDownlinkMsg(queueUpdateMsg)); + } + } return Futures.allAsList(result); } diff --git a/application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java similarity index 64% rename from application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java rename to application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java index cea08003bf..88aa45c74c 100644 --- a/application/src/test/java/org/thingsboard/server/service/cluster/routing/HashPartitionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.cluster.routing; +package org.thingsboard.server.queue.discovery; +import com.datastax.driver.core.utils.UUIDs; import com.datastax.oss.driver.api.core.uuid.Uuids; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Before; @@ -29,17 +31,16 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.HashPartitionService; -import org.thingsboard.server.queue.discovery.QueueRoutingInfoService; -import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; -import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.mockito.Mockito.mock; @@ -71,6 +72,8 @@ public class HashPartitionServiceTest { queueRoutingInfoService); ReflectionTestUtils.setField(clusterRoutingService, "coreTopic", "tb.core"); ReflectionTestUtils.setField(clusterRoutingService, "corePartitions", 10); + ReflectionTestUtils.setField(clusterRoutingService, "vcTopic", "tb.vc"); + ReflectionTestUtils.setField(clusterRoutingService, "vcPartitions", 10); ReflectionTestUtils.setField(clusterRoutingService, "hashFunctionName", hashFunctionName); TransportProtos.ServiceInfo currentServer = TransportProtos.ServiceInfo.newBuilder() .setServiceId("tb-core-0") @@ -109,15 +112,56 @@ public class HashPartitionServiceTest { map.put(partition, map.getOrDefault(partition, 0) + 1); } - List> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList()); + checkDispersion(start, map, ITERATIONS, 5.0); + } + + @SneakyThrows + @Test + public void testDispersionOnResolveByPartitionIdx() { + int serverCount = 5; + int tenantCount = 1000; + int queueCount = 3; + int partitionCount = 3; + + List services = new ArrayList<>(); + + for (int i = 0; i < serverCount; i++) { + services.add(TransportProtos.ServiceInfo.newBuilder().setServiceId("RE-" + i).build()); + } + + long start = System.currentTimeMillis(); + Map map = new HashMap<>(); + services.forEach(s -> map.put(s.getServiceId(), 0)); + + Random random = new Random(); + long ts = new SimpleDateFormat("dd-MM-yyyy").parse("06-12-2016").getTime() - TimeUnit.DAYS.toMillis(tenantCount); + for (int tenantIndex = 0; tenantIndex < tenantCount; tenantIndex++) { + TenantId tenantId = new TenantId(UUIDs.startOf(ts)); + ts += TimeUnit.DAYS.toMillis(1) + random.nextInt(1000); + for (int queueIndex = 0; queueIndex < queueCount; queueIndex++) { + QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "queue" + queueIndex, tenantId); + for (int partition = 0; partition < partitionCount; partition++) { + TransportProtos.ServiceInfo serviceInfo = clusterRoutingService.resolveByPartitionIdx(services, queueKey, partition); + String serviceId = serviceInfo.getServiceId(); + map.put(serviceId, map.get(serviceId) + 1); + } + } + } + + checkDispersion(start, map, tenantCount * queueCount * partitionCount, 10.0); + } + + private void checkDispersion(long start, Map map, int iterations, double maxDiffPercent) { + List> data = map.entrySet().stream().sorted(Comparator.comparingInt(Map.Entry::getValue)).collect(Collectors.toList()); long end = System.currentTimeMillis(); - double diff = (data.get(data.size() - 1).getValue() - data.get(0).getValue()); - double diffPercent = (diff / ITERATIONS) * 100.0; + double ideal = ((double) iterations) / map.size(); + double diff = Math.max(data.get(data.size() - 1).getValue() - ideal, ideal - data.get(0).getValue()); + double diffPercent = (diff / ideal) * 100.0; System.out.println("Time: " + (end - start) + " Diff: " + diff + "(" + String.format("%f", diffPercent) + "%)"); - Assert.assertTrue(diffPercent < 0.5); - for (Map.Entry entry : data) { + for (Map.Entry entry : data) { System.out.println(entry.getKey() + ": " + entry.getValue()); } + Assert.assertTrue(diffPercent < maxDiffPercent); } } diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java index f55190130c..60ebd78b8c 100644 --- a/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructorTest.java @@ -17,41 +17,84 @@ package org.thingsboard.server.service.edge.rpc.constructor; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RuleChainConnectionInfoProto; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleNodeProto; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import java.util.UUID; @Slf4j @RunWith(MockitoJUnitRunner.class) public class RuleChainMsgConstructorTest { - private static final ObjectMapper mapper = new ObjectMapper(); + private RuleChainMsgConstructor constructor; + + private TenantId tenantId; + + @Before + public void setup() { + constructor = new RuleChainMsgConstructor(); + tenantId = new TenantId(UUID.randomUUID()); + } + + @Test + public void testConstructRuleChainMetadataUpdatedMsg_V_3_4_0() throws JsonProcessingException { + RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); + RuleChainMetaData ruleChainMetaData = createRuleChainMetaData( + ruleChainId, 3, createRuleNodes(ruleChainId), createConnections()); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = + constructor.constructRuleChainMetadataUpdatedMsg( + tenantId, + UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, + ruleChainMetaData, + EdgeVersion.V_3_4_0); + + assetV_3_3_3_and_V_3_4_0(ruleChainMetadataUpdateMsg); + + assertCheckpointRuleNodeConfiguration( + ruleChainMetadataUpdateMsg.getNodesList(), + "{\"queueName\":\"HighPriority\"}"); + } @Test public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_3() throws JsonProcessingException { RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); - RuleChainMsgConstructor constructor = new RuleChainMsgConstructor(); - RuleChainMetaData ruleChainMetaData = createRuleChainMetaData(ruleChainId, 3, createRuleNodes(ruleChainId), createConnections()); + RuleChainMetaData ruleChainMetaData = createRuleChainMetaData( + ruleChainId, 3, createRuleNodes(ruleChainId), createConnections()); RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = - constructor.constructRuleChainMetadataUpdatedMsg(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetaData, EdgeVersion.V_3_3_3); + constructor.constructRuleChainMetadataUpdatedMsg( + tenantId, + UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, + ruleChainMetaData, + EdgeVersion.V_3_3_3); + + assetV_3_3_3_and_V_3_4_0(ruleChainMetadataUpdateMsg); + + assertCheckpointRuleNodeConfiguration( + ruleChainMetadataUpdateMsg.getNodesList(), + "{\"queueName\":\"HighPriority\"}"); + } + private void assetV_3_3_3_and_V_3_4_0(RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg) { Assert.assertEquals("First rule node index incorrect!", 3, ruleChainMetadataUpdateMsg.getFirstNodeIndex()); Assert.assertEquals("Nodes count incorrect!", 12, ruleChainMetadataUpdateMsg.getNodesCount()); Assert.assertEquals("Connections count incorrect!", 13, ruleChainMetadataUpdateMsg.getConnectionsCount()); @@ -75,10 +118,13 @@ public class RuleChainMsgConstructorTest { @Test public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_0() throws JsonProcessingException { RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); - RuleChainMsgConstructor constructor = new RuleChainMsgConstructor(); RuleChainMetaData ruleChainMetaData = createRuleChainMetaData(ruleChainId, 3, createRuleNodes(ruleChainId), createConnections()); RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = - constructor.constructRuleChainMetadataUpdatedMsg(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetaData, EdgeVersion.V_3_3_0); + constructor.constructRuleChainMetadataUpdatedMsg( + tenantId, + UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, + ruleChainMetaData, + EdgeVersion.V_3_3_0); Assert.assertEquals("First rule node index incorrect!", 2, ruleChainMetadataUpdateMsg.getFirstNodeIndex()); Assert.assertEquals("Nodes count incorrect!", 10, ruleChainMetadataUpdateMsg.getNodesCount()); @@ -104,16 +150,23 @@ public class RuleChainMsgConstructorTest { ruleChainConnection.getAdditionalInfo()); Assert.assertTrue("Target rule chain id MSB incorrect!", ruleChainConnection.getTargetRuleChainIdMSB() != 0); Assert.assertTrue("Target rule chain id LSB incorrect!", ruleChainConnection.getTargetRuleChainIdLSB() != 0); + + assertCheckpointRuleNodeConfiguration( + ruleChainMetadataUpdateMsg.getNodesList(), + "{\"queueName\":\"HighPriority\"}"); } @Test public void testConstructRuleChainMetadataUpdatedMsg_V_3_3_0_inDifferentOrder() throws JsonProcessingException { // same rule chain metadata, but different order of rule nodes RuleChainId ruleChainId = new RuleChainId(UUID.randomUUID()); - RuleChainMsgConstructor constructor = new RuleChainMsgConstructor(); RuleChainMetaData ruleChainMetaData1 = createRuleChainMetaData(ruleChainId, 8, createRuleNodesInDifferentOrder(ruleChainId), createConnectionsInDifferentOrder()); RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = - constructor.constructRuleChainMetadataUpdatedMsg(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetaData1, EdgeVersion.V_3_3_0); + constructor.constructRuleChainMetadataUpdatedMsg( + tenantId, + UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, + ruleChainMetaData1, + EdgeVersion.V_3_3_0); Assert.assertEquals("First rule node index incorrect!", 7, ruleChainMetadataUpdateMsg.getFirstNodeIndex()); Assert.assertEquals("Nodes count incorrect!", 10, ruleChainMetadataUpdateMsg.getNodesCount()); @@ -139,6 +192,20 @@ public class RuleChainMsgConstructorTest { ruleChainConnection.getAdditionalInfo()); Assert.assertTrue("Target rule chain id MSB incorrect!", ruleChainConnection.getTargetRuleChainIdMSB() != 0); Assert.assertTrue("Target rule chain id LSB incorrect!", ruleChainConnection.getTargetRuleChainIdLSB() != 0); + + assertCheckpointRuleNodeConfiguration( + ruleChainMetadataUpdateMsg.getNodesList(), + "{\"queueName\":\"HighPriority\"}"); + } + + private void assertCheckpointRuleNodeConfiguration(List nodesList, + String expectedConfiguration) { + Optional checkpointRuleNodeOpt = nodesList.stream() + .filter(rn -> "org.thingsboard.rule.engine.flow.TbCheckpointNode".equals(rn.getType())) + .findFirst(); + Assert.assertTrue(checkpointRuleNodeOpt.isPresent()); + RuleNodeProto checkpointRuleNode = checkpointRuleNodeOpt.get(); + Assert.assertEquals(expectedConfiguration, checkpointRuleNode.getConfiguration()); } private void compareNodeConnectionInfoAndProto(NodeConnectionInfo expected, org.thingsboard.server.gen.edge.v1.NodeConnectionInfoProto actual) { @@ -253,8 +320,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbRuleChainOutputNode", "Output node", - mapper.readTree("{\"version\":0}"), - mapper.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":592}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"version\":0}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":592}")); } @NotNull @@ -262,8 +329,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbCheckpointNode", "Checkpoint node", - mapper.readTree("{\"queueName\":\"HighPriority\"}"), - mapper.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":647}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"queueName\":\"HighPriority\"}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":178,\"layoutY\":647}")); } @NotNull @@ -271,8 +338,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", "Save Timeseries", - mapper.readTree("{\"defaultTTL\":0}"), - mapper.readTree("{\"layoutX\":823,\"layoutY\":157}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"defaultTTL\":0}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":823,\"layoutY\":157}")); } @NotNull @@ -280,8 +347,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", "Message Type Switch", - mapper.readTree("{\"version\":0}"), - mapper.readTree("{\"layoutX\":347,\"layoutY\":149}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"version\":0}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":347,\"layoutY\":149}")); } @NotNull @@ -289,8 +356,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.action.TbLogNode", "Log Other", - mapper.readTree("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), - mapper.readTree("{\"layoutX\":824,\"layoutY\":378}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":378}")); } @NotNull @@ -298,8 +365,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode", "Push to cloud", - mapper.readTree("{\"scope\":\"SERVER_SCOPE\"}"), - mapper.readTree("{\"layoutX\":1129,\"layoutY\":52}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"scope\":\"SERVER_SCOPE\"}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":1129,\"layoutY\":52}")); } @NotNull @@ -307,8 +374,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbAckNode", "Acknowledge node", - mapper.readTree("{\"version\":0}"), - mapper.readTree("{\"description\":\"\",\"layoutX\":177,\"layoutY\":703}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"version\":0}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":177,\"layoutY\":703}")); } @NotNull @@ -316,8 +383,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.profile.TbDeviceProfileNode", "Device Profile Node", - mapper.readTree("{\"persistAlarmRulesState\":false,\"fetchAlarmRulesStateOnStart\":false}"), - mapper.readTree("{\"description\":\"Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \\\"Success\\\" relation type.\",\"layoutX\":187,\"layoutY\":468}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"persistAlarmRulesState\":false,\"fetchAlarmRulesStateOnStart\":false}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \\\"Success\\\" relation type.\",\"layoutX\":187,\"layoutY\":468}")); } @NotNull @@ -325,8 +392,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", "Save Client Attributes", - mapper.readTree("{\"scope\":\"CLIENT_SCOPE\"}"), - mapper.readTree("{\"layoutX\":824,\"layoutY\":52}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"scope\":\"CLIENT_SCOPE\"}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":52}")); } @NotNull @@ -334,8 +401,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.action.TbLogNode", "Log RPC from Device", - mapper.readTree("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), - mapper.readTree("{\"layoutX\":825,\"layoutY\":266}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"jsScript\":\"return '\\\\nIncoming message:\\\\n' + JSON.stringify(msg) + '\\\\nIncoming metadata:\\\\n' + JSON.stringify(metadata);\"}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":825,\"layoutY\":266}")); } @NotNull @@ -343,8 +410,8 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode", "RPC Call Request", - mapper.readTree("{\"timeoutInSeconds\":60}"), - mapper.readTree("{\"layoutX\":824,\"layoutY\":466}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"timeoutInSeconds\":60}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"layoutX\":824,\"layoutY\":466}")); } @NotNull @@ -352,7 +419,7 @@ public class RuleChainMsgConstructorTest { return createRuleNode(ruleChainId, "org.thingsboard.rule.engine.flow.TbRuleChainInputNode", "Push to Analytics", - mapper.readTree("{\"ruleChainId\":\"af588000-6c7c-11ec-bafd-c9a47a5c8d99\"}"), - mapper.readTree("{\"description\":\"\",\"layoutX\":477,\"layoutY\":560}")); + JacksonUtil.OBJECT_MAPPER.readTree("{\"ruleChainId\":\"af588000-6c7c-11ec-bafd-c9a47a5c8d99\"}"), + JacksonUtil.OBJECT_MAPPER.readTree("{\"description\":\"\",\"layoutX\":477,\"layoutY\":560}")); } } \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java index 31349ebec4..982fc3483b 100644 --- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java @@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -149,7 +148,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { } @Test - public void sumDataSizeByTenantId() throws ThingsboardException { + public void sumDataSizeByTenantId() throws Exception { Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId)); createResource("test", DEFAULT_FILE_NAME); @@ -165,14 +164,14 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId)); } - private TbResource createResource(String title, String filename) throws ThingsboardException { + private TbResource createResource(String title, String filename) throws Exception { TbResource resource = new TbResource(); resource.setTenantId(tenantId); resource.setTitle(title); resource.setResourceType(ResourceType.JKS); resource.setFileName(filename); resource.setData("1"); - return resourceService.saveResourceInternal(resource); + return resourceService.save(resource); } @Test @@ -184,7 +183,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResource savedResource = resourceService.saveResourceInternal(resource); + TbResource savedResource = resourceService.save(resource); Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource.getId()); @@ -196,7 +195,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { savedResource.setTitle("My new resource"); - resourceService.saveResourceInternal(savedResource); + resourceService.save(savedResource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); Assert.assertEquals(foundResource.getTitle(), savedResource.getTitle()); @@ -211,7 +210,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setFileName("test_model.xml"); resource.setData(Base64.getEncoder().encodeToString(LWM2M_TEST_MODEL.getBytes())); - TbResource savedResource = resourceService.saveResourceInternal(resource); + TbResource savedResource = resourceService.save(resource); Assert.assertNotNull(savedResource); Assert.assertNotNull(savedResource.getId()); @@ -231,7 +230,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResource savedResource = resourceService.saveResourceInternal(resource); + TbResource savedResource = resourceService.save(resource); Assert.assertEquals(TenantId.SYS_TENANT_ID, savedResource.getTenantId()); @@ -247,7 +246,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResource savedResource = resourceService.saveResourceInternal(resource); + TbResource savedResource = resourceService.save(resource); TbResource resource2 = new TbResource(); resource.setTenantId(tenantId); @@ -257,7 +256,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setData("Test Data"); try { - resourceService.saveResourceInternal(resource2); + resourceService.save(resource2); } finally { resourceService.delete(savedResource, null); } @@ -270,7 +269,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - resourceService.saveResourceInternal(resource); + resourceService.save(resource); } @Test(expected = DataValidationException.class) @@ -281,7 +280,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - resourceService.saveResourceInternal(resource); + resourceService.save(resource); } @Test @@ -291,7 +290,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResource savedResource = resourceService.saveResourceInternal(resource); + TbResource savedResource = resourceService.save(resource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); Assert.assertNotNull(foundResource); @@ -307,7 +306,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResource savedResource = resourceService.saveResourceInternal(resource); + TbResource savedResource = resourceService.save(resource); TbResource foundResource = resourceService.getResource(tenantId, savedResource.getResourceType(), savedResource.getResourceKey()); Assert.assertNotNull(foundResource); @@ -322,7 +321,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setTitle("My resource"); resource.setFileName(DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResource savedResource = resourceService.saveResourceInternal(resource); + TbResource savedResource = resourceService.save(resource); TbResource foundResource = resourceService.findResourceById(tenantId, savedResource.getId()); Assert.assertNotNull(foundResource); @@ -348,7 +347,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); resource.setData("Test Data"); - resources.add(new TbResourceInfo(resourceService.saveResourceInternal(resource))); + resources.add(new TbResourceInfo(resourceService.save(resource))); } List loadedResources = new ArrayList<>(); @@ -396,7 +395,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); resource.setData("Test Data"); - TbResourceInfo tbResourceInfo = new TbResourceInfo(resourceService.saveResourceInternal(resource)); + TbResourceInfo tbResourceInfo = new TbResourceInfo(resourceService.save(resource)); if (i >= 50) { resources.add(tbResourceInfo); } @@ -409,7 +408,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest { resource.setResourceType(ResourceType.JKS); resource.setFileName(i + DEFAULT_FILE_NAME); resource.setData("Test Data"); - resources.add(new TbResourceInfo(resourceService.saveResourceInternal(resource))); + resources.add(new TbResourceInfo(resourceService.save(resource))); } List loadedResources = new ArrayList<>(); diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java index 82f3e2ebcd..f804d4dcfd 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; import org.thingsboard.server.common.data.security.model.JwtToken; import org.thingsboard.server.config.JwtSettings; import org.thingsboard.server.dao.customer.CustomerService; @@ -99,7 +100,7 @@ public class TokenOutdatingTest { JwtToken jwtToken = createAccessJwtToken(userId); SECONDS.sleep(1); // need to wait before outdating so that outdatage time is strictly after token issue time - tokenOutdatingService.outdateOldUserTokens(userId); + tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId)); SECONDS.sleep(1); @@ -117,7 +118,7 @@ public class TokenOutdatingTest { }); SECONDS.sleep(1); - tokenOutdatingService.outdateOldUserTokens(userId); + tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); assertThrows(JwtExpiredTokenException.class, () -> { accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken)); @@ -133,7 +134,7 @@ public class TokenOutdatingTest { }); SECONDS.sleep(1); - tokenOutdatingService.outdateOldUserTokens(userId); + tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); assertThrows(CredentialsExpiredException.class, () -> { refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken)); @@ -145,20 +146,20 @@ public class TokenOutdatingTest { JwtToken jwtToken = createAccessJwtToken(userId); SECONDS.sleep(1); - tokenOutdatingService.outdateOldUserTokens(userId); + tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); - int refreshTokenExpirationTime = 5; + int refreshTokenExpirationTime = 3; jwtSettings.setRefreshTokenExpTime(refreshTokenExpirationTime); SECONDS.sleep(refreshTokenExpirationTime - 2); assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId)); - assertNotNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString())); + assertNotNull(cacheManager.getCache(CacheConstants.USERS_UPDATE_TIME_CACHE).get(userId.getId().toString())); SECONDS.sleep(3); assertFalse(tokenOutdatingService.isOutdated(jwtToken, userId)); - assertNull(cacheManager.getCache(CacheConstants.TOKEN_OUTDATAGE_TIME_CACHE).get(userId.getId().toString())); + assertNull(cacheManager.getCache(CacheConstants.USERS_UPDATE_TIME_CACHE).get(userId.getId().toString())); } private JwtToken createAccessJwtToken(UserId userId) { diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java new file mode 100644 index 0000000000..e516d5e131 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/sync/ie/BaseExportImportServiceTest.java @@ -0,0 +1,452 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import org.junit.After; +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode; +import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.asset.Asset; +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.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.sync.ThrowingRunnable; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.relation.RelationService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.model.UserPrincipal; +import org.thingsboard.server.service.sync.vc.data.EntitiesImportCtx; +import org.thingsboard.server.service.sync.vc.data.SimpleEntitiesExportCtx; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public abstract class BaseExportImportServiceTest extends AbstractControllerTest { + + @Autowired + protected EntitiesExportImportService exportImportService; + @Autowired + protected DeviceService deviceService; + @Autowired + protected OtaPackageService otaPackageService; + @Autowired + protected DeviceProfileService deviceProfileService; + @Autowired + protected AssetService assetService; + @Autowired + protected CustomerService customerService; + @Autowired + protected RuleChainService ruleChainService; + @Autowired + protected DashboardService dashboardService; + @Autowired + protected RelationService relationService; + @Autowired + protected TenantService tenantService; + @Autowired + protected EntityViewService entityViewService; + + protected TenantId tenantId1; + protected User tenantAdmin1; + + protected TenantId tenantId2; + protected User tenantAdmin2; + + @Before + public void beforeEach() throws Exception { + loginSysAdmin(); + Tenant tenant1 = new Tenant(); + tenant1.setTitle("Tenant 1"); + tenant1.setEmail("tenant1@thingsboard.org"); + this.tenantId1 = tenantService.saveTenant(tenant1).getId(); + User tenantAdmin1 = new User(); + tenantAdmin1.setTenantId(tenantId1); + tenantAdmin1.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin1.setEmail("tenant1-admin@thingsboard.org"); + this.tenantAdmin1 = createUser(tenantAdmin1, "12345678"); + Tenant tenant2 = new Tenant(); + tenant2.setTitle("Tenant 2"); + tenant2.setEmail("tenant2@thingsboard.org"); + this.tenantId2 = tenantService.saveTenant(tenant2).getId(); + User tenantAdmin2 = new User(); + tenantAdmin2.setTenantId(tenantId2); + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin2.setEmail("tenant2-admin@thingsboard.org"); + this.tenantAdmin2 = createUser(tenantAdmin2, "12345678"); + } + + @After + public void afterEach() { + tenantService.deleteTenant(tenantId1); + tenantService.deleteTenant(tenantId2); + } + + protected Device createDevice(TenantId tenantId, CustomerId customerId, DeviceProfileId deviceProfileId, String name) { + Device device = new Device(); + device.setTenantId(tenantId); + device.setCustomerId(customerId); + device.setName(name); + device.setLabel("lbl"); + device.setDeviceProfileId(deviceProfileId); + DeviceData deviceData = new DeviceData(); + deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration()); + device.setDeviceData(deviceData); + return deviceService.saveDevice(device); + } + + protected OtaPackage createOtaPackage(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType type) { + OtaPackage otaPackage = new OtaPackage(); + otaPackage.setTenantId(tenantId); + otaPackage.setDeviceProfileId(deviceProfileId); + otaPackage.setType(type); + otaPackage.setTitle("My " + type); + otaPackage.setVersion("v1.0"); + otaPackage.setFileName("filename.txt"); + otaPackage.setContentType("text/plain"); + otaPackage.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); + otaPackage.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); + otaPackage.setDataSize(1L); + otaPackage.setData(ByteBuffer.wrap(new byte[]{(int) 1})); + return otaPackageService.saveOtaPackage(otaPackage); + } + + protected void checkImportedDeviceData(Device initialDevice, Device importedDevice) { + assertThat(importedDevice.getName()).isEqualTo(initialDevice.getName()); + assertThat(importedDevice.getType()).isEqualTo(initialDevice.getType()); + assertThat(importedDevice.getDeviceData()).isEqualTo(initialDevice.getDeviceData()); + assertThat(importedDevice.getLabel()).isEqualTo(initialDevice.getLabel()); + } + + protected DeviceProfile createDeviceProfile(TenantId tenantId, RuleChainId defaultRuleChainId, DashboardId defaultDashboardId, String name) { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setTenantId(tenantId); + deviceProfile.setName(name); + deviceProfile.setDescription("dscrptn"); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTransportType(DeviceTransportType.DEFAULT); + deviceProfile.setDefaultRuleChainId(defaultRuleChainId); + deviceProfile.setDefaultDashboardId(defaultDashboardId); + DeviceProfileData profileData = new DeviceProfileData(); + profileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + profileData.setTransportConfiguration(new DefaultDeviceProfileTransportConfiguration()); + deviceProfile.setProfileData(profileData); + return deviceProfileService.saveDeviceProfile(deviceProfile); + } + + protected void checkImportedDeviceProfileData(DeviceProfile initialProfile, DeviceProfile importedProfile) { + assertThat(initialProfile.getName()).isEqualTo(importedProfile.getName()); + assertThat(initialProfile.getType()).isEqualTo(importedProfile.getType()); + assertThat(initialProfile.getTransportType()).isEqualTo(importedProfile.getTransportType()); + assertThat(initialProfile.getProfileData()).isEqualTo(importedProfile.getProfileData()); + assertThat(initialProfile.getDescription()).isEqualTo(importedProfile.getDescription()); + } + + protected Asset createAsset(TenantId tenantId, CustomerId customerId, String type, String name) { + Asset asset = new Asset(); + asset.setTenantId(tenantId); + asset.setCustomerId(customerId); + asset.setType(type); + asset.setName(name); + asset.setLabel("lbl"); + asset.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); + return assetService.saveAsset(asset); + } + + protected void checkImportedAssetData(Asset initialAsset, Asset importedAsset) { + assertThat(importedAsset.getName()).isEqualTo(initialAsset.getName()); + assertThat(importedAsset.getType()).isEqualTo(initialAsset.getType()); + assertThat(importedAsset.getLabel()).isEqualTo(initialAsset.getLabel()); + assertThat(importedAsset.getAdditionalInfo()).isEqualTo(initialAsset.getAdditionalInfo()); + } + + protected Customer createCustomer(TenantId tenantId, String name) { + Customer customer = new Customer(); + customer.setTenantId(tenantId); + customer.setTitle(name); + customer.setCountry("ua"); + customer.setAddress("abb"); + customer.setEmail("ccc@aa.org"); + customer.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); + return customerService.saveCustomer(customer); + } + + protected void checkImportedCustomerData(Customer initialCustomer, Customer importedCustomer) { + assertThat(importedCustomer.getTitle()).isEqualTo(initialCustomer.getTitle()); + assertThat(importedCustomer.getCountry()).isEqualTo(initialCustomer.getCountry()); + assertThat(importedCustomer.getAddress()).isEqualTo(initialCustomer.getAddress()); + assertThat(importedCustomer.getEmail()).isEqualTo(initialCustomer.getEmail()); + } + + protected Dashboard createDashboard(TenantId tenantId, CustomerId customerId, String name) { + Dashboard dashboard = new Dashboard(); + dashboard.setTenantId(tenantId); + dashboard.setTitle(name); + dashboard.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); + dashboard.setImage("abvregewrg"); + dashboard.setMobileHide(true); + dashboard = dashboardService.saveDashboard(dashboard); + if (customerId != null) { + dashboardService.assignDashboardToCustomer(tenantId, dashboard.getId(), customerId); + return dashboardService.findDashboardById(tenantId, dashboard.getId()); + } + return dashboard; + } + + protected Dashboard createDashboard(TenantId tenantId, CustomerId customerId, String name, AssetId assetForEntityAlias) { + Dashboard dashboard = createDashboard(tenantId, customerId, name); + String entityAliases = "{\n" + + "\t\"23c4185d-1497-9457-30b2-6d91e69a5b2c\": {\n" + + "\t\t\"alias\": \"assets\",\n" + + "\t\t\"filter\": {\n" + + "\t\t\t\"entityList\": [\n" + + "\t\t\t\t\"" + assetForEntityAlias.getId().toString() + "\"\n" + + "\t\t\t],\n" + + "\t\t\t\"entityType\": \"ASSET\",\n" + + "\t\t\t\"resolveMultiple\": true,\n" + + "\t\t\t\"type\": \"entityList\"\n" + + "\t\t},\n" + + "\t\t\"id\": \"23c4185d-1497-9457-30b2-6d91e69a5b2c\"\n" + + "\t}\n" + + "}"; + ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode(); + dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases)); + dashboardConfiguration.set("description", new TextNode("hallo")); + dashboard.setConfiguration(dashboardConfiguration); + return dashboardService.saveDashboard(dashboard); + } + + protected void checkImportedDashboardData(Dashboard initialDashboard, Dashboard importedDashboard) { + assertThat(importedDashboard.getTitle()).isEqualTo(initialDashboard.getTitle()); + assertThat(importedDashboard.getConfiguration()).isEqualTo(initialDashboard.getConfiguration()); + assertThat(importedDashboard.getImage()).isEqualTo(initialDashboard.getImage()); + assertThat(importedDashboard.isMobileHide()).isEqualTo(initialDashboard.isMobileHide()); + if (initialDashboard.getAssignedCustomers() != null) { + assertThat(importedDashboard.getAssignedCustomers()).containsAll(initialDashboard.getAssignedCustomers()); + } + } + protected RuleChain createRuleChain(TenantId tenantId, String name, EntityId originatorId) { + RuleChain ruleChain = new RuleChain(); + ruleChain.setTenantId(tenantId); + ruleChain.setName(name); + ruleChain.setType(RuleChainType.CORE); + ruleChain.setDebugMode(true); + ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); + ruleChain = ruleChainService.saveRuleChain(ruleChain); + + RuleChainMetaData metaData = new RuleChainMetaData(); + metaData.setRuleChainId(ruleChain.getId()); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("Generator 1"); + ruleNode1.setType(TbMsgGeneratorNode.class.getName()); + ruleNode1.setDebugMode(true); + TbMsgGeneratorNodeConfiguration configuration1 = new TbMsgGeneratorNodeConfiguration(); + configuration1.setOriginatorType(originatorId.getEntityType()); + configuration1.setOriginatorId(originatorId.getId().toString()); + ruleNode1.setConfiguration(mapper.valueToTree(configuration1)); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("Simple Rule Node 2"); + ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode2.setDebugMode(true); + TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); + configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); + ruleNode2.setConfiguration(mapper.valueToTree(configuration2)); + + metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); + metaData.setFirstNodeIndex(0); + metaData.addConnectionInfo(0, 1, "Success"); + ruleChainService.saveRuleChainMetaData(tenantId, metaData); + + return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); + } + + protected RuleChain createRuleChain(TenantId tenantId, String name) { + RuleChain ruleChain = new RuleChain(); + ruleChain.setTenantId(tenantId); + ruleChain.setName(name); + ruleChain.setType(RuleChainType.CORE); + ruleChain.setDebugMode(true); + ruleChain.setConfiguration(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); + ruleChain = ruleChainService.saveRuleChain(ruleChain); + + RuleChainMetaData metaData = new RuleChainMetaData(); + metaData.setRuleChainId(ruleChain.getId()); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("Simple Rule Node 1"); + ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode1.setDebugMode(true); + TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); + configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); + ruleNode1.setConfiguration(mapper.valueToTree(configuration1)); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("Simple Rule Node 2"); + ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode2.setDebugMode(true); + TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); + configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); + ruleNode2.setConfiguration(mapper.valueToTree(configuration2)); + + metaData.setNodes(Arrays.asList(ruleNode1, ruleNode2)); + metaData.setFirstNodeIndex(0); + metaData.addConnectionInfo(0, 1, "Success"); + ruleChainService.saveRuleChainMetaData(tenantId, metaData); + + return ruleChainService.findRuleChainById(tenantId, ruleChain.getId()); + } + + protected void checkImportedRuleChainData(RuleChain initialRuleChain, RuleChainMetaData initialMetaData, RuleChain importedRuleChain, RuleChainMetaData importedMetaData) { + assertThat(importedRuleChain.getType()).isEqualTo(initialRuleChain.getType()); + assertThat(importedRuleChain.getName()).isEqualTo(initialRuleChain.getName()); + assertThat(importedRuleChain.isDebugMode()).isEqualTo(initialRuleChain.isDebugMode()); + assertThat(importedRuleChain.getConfiguration()).isEqualTo(initialRuleChain.getConfiguration()); + + assertThat(importedMetaData.getConnections()).isEqualTo(initialMetaData.getConnections()); + assertThat(importedMetaData.getFirstNodeIndex()).isEqualTo(initialMetaData.getFirstNodeIndex()); + for (int i = 0; i < initialMetaData.getNodes().size(); i++) { + RuleNode initialNode = initialMetaData.getNodes().get(i); + RuleNode importedNode = importedMetaData.getNodes().get(i); + assertThat(importedNode.getRuleChainId()).isEqualTo(importedRuleChain.getId()); + assertThat(importedNode.getName()).isEqualTo(initialNode.getName()); + assertThat(importedNode.getType()).isEqualTo(initialNode.getType()); + assertThat(importedNode.getConfiguration()).isEqualTo(initialNode.getConfiguration()); + assertThat(importedNode.getAdditionalInfo()).isEqualTo(initialNode.getAdditionalInfo()); + } + } + + protected EntityView createEntityView(TenantId tenantId, CustomerId customerId, EntityId entityId, String name) { + EntityView entityView = new EntityView(); + entityView.setTenantId(tenantId); + entityView.setEntityId(entityId); + entityView.setCustomerId(customerId); + entityView.setName(name); + entityView.setType("A"); + return entityViewService.saveEntityView(entityView); + } + + protected EntityRelation createRelation(EntityId from, EntityId to) { + EntityRelation relation = new EntityRelation(); + relation.setFrom(from); + relation.setTo(to); + relation.setType(EntityRelation.MANAGES_TYPE); + relation.setAdditionalInfo(JacksonUtil.newObjectNode().set("a", new TextNode("b"))); + relation.setTypeGroup(RelationTypeGroup.COMMON); + relationService.saveRelation(TenantId.SYS_TENANT_ID, relation); + return relation; + } + + protected & HasTenantId> void checkImportedEntity(TenantId tenantId1, E initialEntity, TenantId tenantId2, E importedEntity) { + assertThat(initialEntity.getTenantId()).isEqualTo(tenantId1); + assertThat(importedEntity.getTenantId()).isEqualTo(tenantId2); + + assertThat(importedEntity.getExternalId()).isEqualTo(initialEntity.getId()); + + boolean sameTenant = tenantId1.equals(tenantId2); + if (!sameTenant) { + assertThat(importedEntity.getId()).isNotEqualTo(initialEntity.getId()); + } else { + assertThat(importedEntity.getId()).isEqualTo(initialEntity.getId()); + } + } + + + protected , I extends EntityId> EntityExportData exportEntity(User user, I entityId) throws Exception { + return exportEntity(user, entityId, EntityExportSettings.builder() + .exportCredentials(true) + .build()); + } + + protected , I extends EntityId> EntityExportData exportEntity(User user, I entityId, EntityExportSettings exportSettings) throws Exception { + return exportImportService.exportEntity(new SimpleEntitiesExportCtx(getSecurityUser(user), null, null, exportSettings), entityId); + } + + protected , I extends EntityId> EntityImportResult importEntity(User user, EntityExportData exportData) throws Exception { + return importEntity(user, exportData, EntityImportSettings.builder() + .saveCredentials(true) + .build()); + } + + protected , I extends EntityId> EntityImportResult importEntity(User user, EntityExportData exportData, EntityImportSettings importSettings) throws Exception { + EntitiesImportCtx ctx = new EntitiesImportCtx(UUID.randomUUID(), getSecurityUser(user), null, importSettings); + ctx.setFinalImportAttempt(true); + exportData = JacksonUtil.treeToValue(JacksonUtil.valueToTree(exportData), EntityExportData.class); + EntityImportResult importResult = exportImportService.importEntity(ctx, exportData); + exportImportService.saveReferencesAndRelations(ctx); + for (ThrowingRunnable throwingRunnable : ctx.getEventCallbacks()) { + throwingRunnable.run(); + } + return importResult; + } + + protected SecurityUser getSecurityUser(User user) { + return new SecurityUser(user, true, new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail())); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java b/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java new file mode 100644 index 0000000000..8cf57e8e37 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/sync/ie/ExportImportServiceSqlTest.java @@ -0,0 +1,585 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.ie; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import com.google.common.collect.Streams; +import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode; +import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.sync.ie.DeviceExportData; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; +import org.thingsboard.server.common.data.sync.ie.EntityExportSettings; +import org.thingsboard.server.common.data.sync.ie.EntityImportResult; +import org.thingsboard.server.common.data.sync.ie.EntityImportSettings; +import org.thingsboard.server.common.data.sync.ie.RuleChainExportData; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.dao.device.DeviceProfileDao; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.service.action.EntityActionService; +import org.thingsboard.server.service.ota.OtaPackageStateService; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.verify; + +@DaoSqlTest +public class ExportImportServiceSqlTest extends BaseExportImportServiceTest { + + @Autowired + private DeviceCredentialsService deviceCredentialsService; + @SpyBean + private EntityActionService entityActionService; + @SpyBean + private OtaPackageStateService otaPackageStateService; + + @Test + public void testExportImportAsset_betweenTenants() throws Exception { + Asset asset = createAsset(tenantId1, null, "AB", "Asset of tenant 1"); + EntityExportData exportData = exportEntity(tenantAdmin1, asset.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin2, exportData); + checkImportedEntity(tenantId1, asset, tenantId2, importResult.getSavedEntity()); + checkImportedAssetData(asset, importResult.getSavedEntity()); + } + + @Test + public void testExportImportAsset_sameTenant() throws Exception { + Asset asset = createAsset(tenantId1, null, "AB", "Asset v1.0"); + EntityExportData exportData = exportEntity(tenantAdmin1, asset.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin1, exportData); + checkImportedEntity(tenantId1, asset, tenantId1, importResult.getSavedEntity()); + checkImportedAssetData(asset, importResult.getSavedEntity()); + } + + @Test + public void testExportImportAsset_sameTenant_withCustomer() throws Exception { + Customer customer = createCustomer(tenantId1, "My customer"); + Asset asset = createAsset(tenantId1, customer.getId(), "AB", "My asset"); + + Asset importedAsset = importEntity(tenantAdmin1, this.exportEntity(tenantAdmin1, asset.getId())).getSavedEntity(); + assertThat(importedAsset.getCustomerId()).isEqualTo(asset.getCustomerId()); + } + + + @Test + public void testExportImportCustomer_betweenTenants() throws Exception { + Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer of tenant 1"); + EntityExportData exportData = exportEntity(tenantAdmin1, customer.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin2, exportData); + checkImportedEntity(tenantId1, customer, tenantId2, importResult.getSavedEntity()); + checkImportedCustomerData(customer, importResult.getSavedEntity()); + } + + @Test + public void testExportImportCustomer_sameTenant() throws Exception { + Customer customer = createCustomer(tenantAdmin1.getTenantId(), "Customer v1.0"); + EntityExportData exportData = exportEntity(tenantAdmin1, customer.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin1, exportData); + checkImportedEntity(tenantId1, customer, tenantId1, importResult.getSavedEntity()); + checkImportedCustomerData(customer, importResult.getSavedEntity()); + } + + + @Test + public void testExportImportDeviceWithProfile_betweenTenants() throws Exception { + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, null, null, "Device profile of tenant 1"); + Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device of tenant 1"); + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId()); + + EntityExportData profileExportData = exportEntity(tenantAdmin1, deviceProfile.getId()); + + EntityExportData deviceExportData = exportEntity(tenantAdmin1, device.getId()); + DeviceCredentials exportedCredentials = ((DeviceExportData) deviceExportData).getCredentials(); + exportedCredentials.setCredentialsId(credentials.getCredentialsId() + "a"); + + EntityImportResult profileImportResult = importEntity(tenantAdmin2, profileExportData); + checkImportedEntity(tenantId1, deviceProfile, tenantId2, profileImportResult.getSavedEntity()); + checkImportedDeviceProfileData(deviceProfile, profileImportResult.getSavedEntity()); + + EntityImportResult deviceImportResult = importEntity(tenantAdmin2, deviceExportData); + Device importedDevice = deviceImportResult.getSavedEntity(); + checkImportedEntity(tenantId1, device, tenantId2, deviceImportResult.getSavedEntity()); + checkImportedDeviceData(device, importedDevice); + + assertThat(importedDevice.getDeviceProfileId()).isEqualTo(profileImportResult.getSavedEntity().getId()); + + DeviceCredentials importedCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId2, importedDevice.getId()); + assertThat(importedCredentials.getId()).isNotEqualTo(credentials.getId()); + assertThat(importedCredentials.getCredentialsId()).isEqualTo(exportedCredentials.getCredentialsId()); + assertThat(importedCredentials.getCredentialsValue()).isEqualTo(credentials.getCredentialsValue()); + assertThat(importedCredentials.getCredentialsType()).isEqualTo(credentials.getCredentialsType()); + } + + @Test + public void testExportImportDevice_sameTenant() throws Exception { + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, null, null, "Device profile v1.0"); + OtaPackage firmware = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.FIRMWARE); + OtaPackage software = createOtaPackage(tenantId1, deviceProfile.getId(), OtaPackageType.SOFTWARE); + Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device v1.0"); + device.setFirmwareId(firmware.getId()); + device.setSoftwareId(software.getId()); + device = deviceService.saveDevice(device); + + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId()); + + EntityExportData deviceExportData = exportEntity(tenantAdmin1, device.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin1, deviceExportData); + Device importedDevice = importResult.getSavedEntity(); + + checkImportedEntity(tenantId1, device, tenantId1, importResult.getSavedEntity()); + assertThat(importedDevice.getDeviceProfileId()).isEqualTo(device.getDeviceProfileId()); + assertThat(deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId1, device.getId())).isEqualTo(credentials); + assertThat(importedDevice.getFirmwareId()).isEqualTo(firmware.getId()); + assertThat(importedDevice.getSoftwareId()).isEqualTo(software.getId()); + } + + + @Test + public void testExportImportDashboard_betweenTenants() throws Exception { + Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1"); + EntityExportData exportData = exportEntity(tenantAdmin1, dashboard.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin2, exportData); + checkImportedEntity(tenantId1, dashboard, tenantId2, importResult.getSavedEntity()); + checkImportedDashboardData(dashboard, importResult.getSavedEntity()); + } + + @Test + public void testExportImportDashboard_sameTenant() throws Exception { + Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard v1.0"); + EntityExportData exportData = exportEntity(tenantAdmin1, dashboard.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin1, exportData); + checkImportedEntity(tenantId1, dashboard, tenantId1, importResult.getSavedEntity()); + checkImportedDashboardData(dashboard, importResult.getSavedEntity()); + } + + @Test + public void testExportImportDashboard_betweenTenants_withCustomer_updated() throws Exception { + Dashboard dashboard = createDashboard(tenantAdmin1.getTenantId(), null, "Dashboard of tenant 1"); + EntityExportData exportData = exportEntity(tenantAdmin1, dashboard.getId()); + + Dashboard importedDashboard = importEntity(tenantAdmin2, exportData).getSavedEntity(); + checkImportedEntity(tenantId1, dashboard, tenantId2, importedDashboard); + + Customer customer = createCustomer(tenantId1, "Customer 1"); + EntityExportData customerExportData = exportEntity(tenantAdmin1, customer.getId()); + dashboardService.assignDashboardToCustomer(tenantId1, dashboard.getId(), customer.getId()); + exportData = exportEntity(tenantAdmin1, dashboard.getId()); + + Customer importedCustomer = importEntity(tenantAdmin2, customerExportData).getSavedEntity(); + importedDashboard = importEntity(tenantAdmin2, exportData).getSavedEntity(); + assertThat(importedDashboard.getAssignedCustomers()).hasOnlyOneElementSatisfying(customerInfo -> { + assertThat(customerInfo.getCustomerId()).isEqualTo(importedCustomer.getId()); + }); + } + + @Test + public void testExportImportDashboard_betweenTenants_withEntityAliases() throws Exception { + Asset asset1 = createAsset(tenantId1, null, "A", "Asset 1"); + Asset asset2 = createAsset(tenantId1, null, "A", "Asset 2"); + Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1"); + + String entityAliases = "{\n" + + "\t\"23c4185d-1497-9457-30b2-6d91e69a5b2c\": {\n" + + "\t\t\"alias\": \"assets\",\n" + + "\t\t\"filter\": {\n" + + "\t\t\t\"entityList\": [\n" + + "\t\t\t\t\"" + asset1.getId().toString() + "\",\n" + + "\t\t\t\t\"" + asset2.getId().toString() + "\"\n" + + "\t\t\t],\n" + + "\t\t\t\"entityType\": \"ASSET\",\n" + + "\t\t\t\"resolveMultiple\": true,\n" + + "\t\t\t\"type\": \"entityList\"\n" + + "\t\t},\n" + + "\t\t\"id\": \"23c4185d-1497-9457-30b2-6d91e69a5b2c\"\n" + + "\t}\n" + + "}"; + ObjectNode dashboardConfiguration = JacksonUtil.newObjectNode(); + dashboardConfiguration.set("entityAliases", JacksonUtil.toJsonNode(entityAliases)); + dashboardConfiguration.set("description", new TextNode("hallo")); + dashboard.setConfiguration(dashboardConfiguration); + dashboard = dashboardService.saveDashboard(dashboard); + + EntityExportData asset1ExportData = exportEntity(tenantAdmin1, asset1.getId()); + EntityExportData asset2ExportData = exportEntity(tenantAdmin1, asset2.getId()); + EntityExportData dashboardExportData = exportEntity(tenantAdmin1, dashboard.getId()); + + Asset importedAsset1 = importEntity(tenantAdmin2, asset1ExportData).getSavedEntity(); + Asset importedAsset2 = importEntity(tenantAdmin2, asset2ExportData).getSavedEntity(); + Dashboard importedDashboard = importEntity(tenantAdmin2, dashboardExportData).getSavedEntity(); + + Set entityAliasEntitiesIds = Streams.stream(importedDashboard.getConfiguration() + .get("entityAliases").elements().next().get("filter").get("entityList").elements()) + .map(JsonNode::asText).collect(Collectors.toSet()); + assertThat(entityAliasEntitiesIds).doesNotContain(asset1.getId().toString(), asset2.getId().toString()); + assertThat(entityAliasEntitiesIds).contains(importedAsset1.getId().toString(), importedAsset2.getId().toString()); + } + + + @Test + public void testExportImportRuleChain_betweenTenants() throws Exception { + RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain of tenant 1"); + RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId()); + EntityExportData exportData = exportEntity(tenantAdmin1, ruleChain.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin2, exportData); + RuleChain importedRuleChain = importResult.getSavedEntity(); + RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId2, importedRuleChain.getId()); + + checkImportedEntity(tenantId1, ruleChain, tenantId2, importResult.getSavedEntity()); + checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData); + } + + @Test + public void testExportImportRuleChain_sameTenant() throws Exception { + RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain v1.0"); + RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId1, ruleChain.getId()); + EntityExportData exportData = exportEntity(tenantAdmin1, ruleChain.getId()); + + EntityImportResult importResult = importEntity(tenantAdmin1, exportData); + RuleChain importedRuleChain = importResult.getSavedEntity(); + RuleChainMetaData importedMetaData = ruleChainService.loadRuleChainMetaData(tenantId1, importedRuleChain.getId()); + + checkImportedEntity(tenantId1, ruleChain, tenantId1, importResult.getSavedEntity()); + checkImportedRuleChainData(ruleChain, metaData, importedRuleChain, importedMetaData); + } + + + @Test + public void testExportImportWithInboundRelations_betweenTenants() throws Exception { + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + Device device = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation = createRelation(asset.getId(), device.getId()); + + EntityExportData assetExportData = exportEntity(tenantAdmin1, asset.getId()); + EntityExportData deviceExportData = exportEntity(tenantAdmin1, device.getId(), EntityExportSettings.builder() + .exportRelations(true) + .exportCredentials(false) + .build()); + + assertThat(deviceExportData.getRelations()).size().isOne(); + assertThat(deviceExportData.getRelations().get(0)).matches(entityRelation -> { + return entityRelation.getFrom().equals(asset.getId()) && entityRelation.getTo().equals(device.getId()); + }); + ((Device) deviceExportData.getEntity()).setDeviceProfileId(null); + + Asset importedAsset = importEntity(tenantAdmin2, assetExportData).getSavedEntity(); + Device importedDevice = importEntity(tenantAdmin2, deviceExportData, EntityImportSettings.builder() + .updateRelations(true) + .build()).getSavedEntity(); + checkImportedEntity(tenantId1, device, tenantId2, importedDevice); + checkImportedEntity(tenantId1, asset, tenantId2, importedAsset); + + List importedRelations = relationService.findByTo(TenantId.SYS_TENANT_ID, importedDevice.getId(), RelationTypeGroup.COMMON); + assertThat(importedRelations).size().isOne(); + assertThat(importedRelations.get(0)).satisfies(importedRelation -> { + assertThat(importedRelation.getFrom()).isEqualTo(importedAsset.getId()); + assertThat(importedRelation.getType()).isEqualTo(relation.getType()); + assertThat(importedRelation.getAdditionalInfo()).isEqualTo(relation.getAdditionalInfo()); + }); + } + + @Test + public void testExportImportWithRelations_betweenTenants() throws Exception { + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + Device device = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation = createRelation(asset.getId(), device.getId()); + + EntityExportData assetExportData = exportEntity(tenantAdmin1, asset.getId()); + EntityExportData deviceExportData = exportEntity(tenantAdmin1, device.getId(), EntityExportSettings.builder() + .exportRelations(true) + .exportCredentials(false) + .build()); + deviceExportData.getEntity().setDeviceProfileId(null); + + Asset importedAsset = importEntity(tenantAdmin2, assetExportData).getSavedEntity(); + Device importedDevice = importEntity(tenantAdmin2, deviceExportData, EntityImportSettings.builder() + .updateRelations(true) + .build()).getSavedEntity(); + + List importedRelations = relationService.findByTo(TenantId.SYS_TENANT_ID, importedDevice.getId(), RelationTypeGroup.COMMON); + assertThat(importedRelations).size().isOne(); + assertThat(importedRelations.get(0)).satisfies(importedRelation -> { + assertThat(importedRelation.getFrom()).isEqualTo(importedAsset.getId()); + assertThat(importedRelation.getType()).isEqualTo(relation.getType()); + assertThat(importedRelation.getAdditionalInfo()).isEqualTo(relation.getAdditionalInfo()); + }); + } + + @Test + public void testExportImportWithRelations_sameTenant() throws Exception { + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + Device device1 = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation1 = createRelation(asset.getId(), device1.getId()); + + EntityExportData assetExportData = exportEntity(tenantAdmin1, asset.getId(), EntityExportSettings.builder() + .exportRelations(true) + .build()); + assertThat(assetExportData.getRelations()).size().isOne(); + + Device device2 = createDevice(tenantId1, null, null, "Device 2"); + EntityRelation relation2 = createRelation(asset.getId(), device2.getId()); + + importEntity(tenantAdmin1, assetExportData, EntityImportSettings.builder() + .updateRelations(true) + .build()); + + List relations = relationService.findByFrom(TenantId.SYS_TENANT_ID, asset.getId(), RelationTypeGroup.COMMON); + assertThat(relations).contains(relation1); + assertThat(relations).doesNotContain(relation2); + } + + @Test + public void textExportImportWithRelations_sameTenant_removeExisting() throws Exception { + Asset asset1 = createAsset(tenantId1, null, "A", "Asset 1"); + Device device = createDevice(tenantId1, null, null, "Device 1"); + EntityRelation relation1 = createRelation(asset1.getId(), device.getId()); + + EntityExportData deviceExportData = exportEntity(tenantAdmin1, device.getId(), EntityExportSettings.builder() + .exportRelations(true) + .build()); + assertThat(deviceExportData.getRelations()).size().isOne(); + + Asset asset2 = createAsset(tenantId1, null, "A", "Asset 2"); + EntityRelation relation2 = createRelation(asset2.getId(), device.getId()); + + importEntity(tenantAdmin1, deviceExportData, EntityImportSettings.builder() + .updateRelations(true) + .build()); + + List relations = relationService.findByTo(TenantId.SYS_TENANT_ID, device.getId(), RelationTypeGroup.COMMON); + assertThat(relations).contains(relation1); + assertThat(relations).doesNotContain(relation2); + } + + + @Test + public void testExportImportDeviceProfile_betweenTenants_findExistingByName() throws Exception { + DeviceProfile defaultDeviceProfile = deviceProfileService.findDefaultDeviceProfile(tenantId1); + EntityExportData deviceProfileExportData = exportEntity(tenantAdmin1, defaultDeviceProfile.getId()); + + assertThatThrownBy(() -> { + importEntity(tenantAdmin2, deviceProfileExportData, EntityImportSettings.builder() + .findExistingByName(false) + .build()); + }).hasMessageContaining("default device profile is present"); + + importEntity(tenantAdmin2, deviceProfileExportData, EntityImportSettings.builder() + .findExistingByName(true) + .build()); + checkImportedEntity(tenantId1, defaultDeviceProfile, tenantId2, deviceProfileService.findDefaultDeviceProfile(tenantId2)); + } + + + @SuppressWarnings("rawTypes") + private static EntityExportData getAndClone(Map map, EntityType entityType) { + return JacksonUtil.clone(map.get(entityType)); + } + + @SuppressWarnings({"rawTypes", "unchecked"}) + @Test + public void testEntityEventsOnImport() throws Exception { + Customer customer = createCustomer(tenantId1, "Customer 1"); + Asset asset = createAsset(tenantId1, null, "A", "Asset 1"); + RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1"); + Dashboard dashboard = createDashboard(tenantId1, null, "Dashboard 1"); + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, ruleChain.getId(), dashboard.getId(), "Device profile 1"); + Device device = createDevice(tenantId1, null, deviceProfile.getId(), "Device 1"); + + Map entitiesExportData = Stream.of(customer.getId(), asset.getId(), device.getId(), + ruleChain.getId(), dashboard.getId(), deviceProfile.getId()) + .map(entityId -> { + try { + return exportEntity(tenantAdmin1, entityId, EntityExportSettings.builder() + .exportCredentials(false) + .build()); + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toMap(EntityExportData::getEntityType, d -> d)); + + Mockito.reset(entityActionService); + Customer importedCustomer = (Customer) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.CUSTOMER)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedCustomer.getId()), eq(importedCustomer), + any(), eq(ActionType.ADDED), isNull()); + Mockito.reset(entityActionService); + importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.CUSTOMER)); + verify(entityActionService, Mockito.never()).logEntityAction(any(), eq(importedCustomer.getId()), eq(importedCustomer), + any(), eq(ActionType.UPDATED), isNull()); + + EntityExportData updatedCustomerEntity = getAndClone(entitiesExportData, EntityType.CUSTOMER); + updatedCustomerEntity.getEntity().setEmail("t" + updatedCustomerEntity.getEntity().getEmail()); + Customer updatedCustomer = importEntity(tenantAdmin2, updatedCustomerEntity).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedCustomer.getId()), eq(updatedCustomer), + any(), eq(ActionType.UPDATED), isNull()); + verify(tbClusterService).sendNotificationMsgToEdge(any(), any(), eq(importedCustomer.getId()), any(), any(), eq(EdgeEventActionType.UPDATED)); + + Mockito.reset(entityActionService); + + Asset importedAsset = (Asset) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.ASSET)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedAsset.getId()), eq(importedAsset), + any(), eq(ActionType.ADDED), isNull()); + importEntity(tenantAdmin2, entitiesExportData.get(EntityType.ASSET)); + verify(entityActionService, Mockito.never()).logEntityAction(any(), eq(importedAsset.getId()), eq(importedAsset), + any(), eq(ActionType.UPDATED), isNull()); + + + EntityExportData updatedAssetEntity = getAndClone(entitiesExportData, EntityType.ASSET); + updatedAssetEntity.getEntity().setLabel("t" + updatedAssetEntity.getEntity().getLabel()); + Asset updatedAsset = importEntity(tenantAdmin2, updatedAssetEntity).getSavedEntity(); + + verify(entityActionService).logEntityAction(any(), eq(importedAsset.getId()), eq(updatedAsset), + any(), eq(ActionType.UPDATED), isNull()); + verify(tbClusterService).sendNotificationMsgToEdge(any(), any(), eq(importedAsset.getId()), any(), any(), eq(EdgeEventActionType.UPDATED)); + + RuleChain importedRuleChain = (RuleChain) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.RULE_CHAIN)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedRuleChain.getId()), eq(importedRuleChain), + any(), eq(ActionType.ADDED), isNull()); + verify(tbClusterService).broadcastEntityStateChangeEvent(any(), eq(importedRuleChain.getId()), eq(ComponentLifecycleEvent.CREATED)); + + Dashboard importedDashboard = (Dashboard) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DASHBOARD)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedDashboard.getId()), eq(importedDashboard), + any(), eq(ActionType.ADDED), isNull()); + + DeviceProfile importedDeviceProfile = (DeviceProfile) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DEVICE_PROFILE)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedDeviceProfile.getId()), eq(importedDeviceProfile), + any(), eq(ActionType.ADDED), isNull()); + verify(tbClusterService).onDeviceProfileChange(eq(importedDeviceProfile), any()); + verify(tbClusterService).broadcastEntityStateChangeEvent(any(), eq(importedDeviceProfile.getId()), eq(ComponentLifecycleEvent.CREATED)); + verify(tbClusterService).sendNotificationMsgToEdge(any(), any(), eq(importedDeviceProfile.getId()), any(), any(), eq(EdgeEventActionType.ADDED)); + verify(otaPackageStateService).update(eq(importedDeviceProfile), eq(false), eq(false)); + + Device importedDevice = (Device) importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DEVICE)).getSavedEntity(); + verify(entityActionService).logEntityAction(any(), eq(importedDevice.getId()), eq(importedDevice), + any(), eq(ActionType.ADDED), isNull()); + verify(tbClusterService).onDeviceUpdated(eq(importedDevice), isNull()); + importEntity(tenantAdmin2, getAndClone(entitiesExportData, EntityType.DEVICE)); + verify(tbClusterService, Mockito.never()).onDeviceUpdated(eq(importedDevice), eq(importedDevice)); + + EntityExportData updatedDeviceEntity = getAndClone(entitiesExportData, EntityType.DEVICE); + updatedDeviceEntity.getEntity().setLabel("t" + updatedDeviceEntity.getEntity().getLabel()); + Device updatedDevice = importEntity(tenantAdmin2, updatedDeviceEntity).getSavedEntity(); + verify(tbClusterService).onDeviceUpdated(eq(updatedDevice), eq(importedDevice)); + } + + @Test + public void testExternalIdsInExportData() throws Exception { + Customer customer = createCustomer(tenantId1, "Customer 1"); + Asset asset = createAsset(tenantId1, customer.getId(), "A", "Asset 1"); + RuleChain ruleChain = createRuleChain(tenantId1, "Rule chain 1", asset.getId()); + Dashboard dashboard = createDashboard(tenantId1, customer.getId(), "Dashboard 1", asset.getId()); + DeviceProfile deviceProfile = createDeviceProfile(tenantId1, ruleChain.getId(), dashboard.getId(), "Device profile 1"); + Device device = createDevice(tenantId1, customer.getId(), deviceProfile.getId(), "Device 1"); + EntityView entityView = createEntityView(tenantId1, customer.getId(), device.getId(), "Entity view 1"); + + Map ids = new HashMap<>(); + for (EntityId entityId : List.of(customer.getId(), asset.getId(), ruleChain.getId(), dashboard.getId(), + deviceProfile.getId(), device.getId(), entityView.getId(), ruleChain.getId(), dashboard.getId())) { + EntityExportData exportData = exportEntity(getSecurityUser(tenantAdmin1), entityId); + EntityImportResult importResult = importEntity(getSecurityUser(tenantAdmin2), exportData, EntityImportSettings.builder() + .saveCredentials(false) + .build()); + ids.put(entityId, (EntityId) importResult.getSavedEntity().getId()); + } + + Asset exportedAsset = (Asset) exportEntity(tenantAdmin2, (AssetId) ids.get(asset.getId())).getEntity(); + assertThat(exportedAsset.getCustomerId()).isEqualTo(customer.getId()); + + EntityExportData ruleChainExportData = exportEntity(tenantAdmin2, (RuleChainId) ids.get(ruleChain.getId())); + TbMsgGeneratorNodeConfiguration exportedRuleNodeConfig = ((RuleChainExportData) ruleChainExportData).getMetaData().getNodes().stream() + .filter(node -> node.getType().equals(TbMsgGeneratorNode.class.getName())).findFirst() + .map(RuleNode::getConfiguration).map(config -> JacksonUtil.treeToValue(config, TbMsgGeneratorNodeConfiguration.class)).orElse(null); + assertThat(exportedRuleNodeConfig.getOriginatorId()).isEqualTo(asset.getId().toString()); + + Dashboard exportedDashboard = (Dashboard) exportEntity(tenantAdmin2, (DashboardId) ids.get(dashboard.getId())).getEntity(); + assertThat(exportedDashboard.getAssignedCustomers()).hasOnlyOneElementSatisfying(shortCustomerInfo -> { + assertThat(shortCustomerInfo.getCustomerId()).isEqualTo(customer.getId()); + }); + String exportedEntityAliasAssetId = exportedDashboard.getConfiguration().get("entityAliases").elements().next() + .get("filter").get("entityList").elements().next().asText(); + assertThat(exportedEntityAliasAssetId).isEqualTo(asset.getId().toString()); + + DeviceProfile exportedDeviceProfile = (DeviceProfile) exportEntity(tenantAdmin2, (DeviceProfileId) ids.get(deviceProfile.getId())).getEntity(); + assertThat(exportedDeviceProfile.getDefaultRuleChainId()).isEqualTo(ruleChain.getId()); + assertThat(exportedDeviceProfile.getDefaultDashboardId()).isEqualTo(dashboard.getId()); + + Device exportedDevice = (Device) exportEntity(tenantAdmin2, (DeviceId) ids.get(device.getId())).getEntity(); + assertThat(exportedDevice.getCustomerId()).isEqualTo(customer.getId()); + assertThat(exportedDevice.getDeviceProfileId()).isEqualTo(deviceProfile.getId()); + + EntityView exportedEntityView = (EntityView) exportEntity(tenantAdmin2, (EntityViewId) ids.get(entityView.getId())).getEntity(); + assertThat(exportedEntityView.getCustomerId()).isEqualTo(customer.getId()); + assertThat(exportedEntityView.getEntityId()).isEqualTo(device.getId()); + + deviceProfile.setDefaultDashboardId(null); + deviceProfileService.saveDeviceProfile(deviceProfile); + DeviceProfile importedDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId2, (DeviceProfileId) ids.get(deviceProfile.getId())); + importedDeviceProfile.setDefaultDashboardId(null); + deviceProfileService.saveDeviceProfile(importedDeviceProfile); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java index b3ab39fd51..ce43ae3bb5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java @@ -16,8 +16,6 @@ package org.thingsboard.server.transport.coap; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; -import org.junit.After; import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.Device; @@ -41,7 +39,6 @@ import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadCon import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.transport.AbstractTransportIntegrationTest; import static org.junit.Assert.assertEquals; @@ -54,12 +51,11 @@ import static org.junit.Assert.assertNotNull; public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest { protected final byte[] EMPTY_PAYLOAD = new byte[0]; - - protected CoapClient client; + protected CoapTestClient client; protected void processAfterTest() throws Exception { if (client != null) { - client.shutdown(); + client.disconnect(); } } @@ -156,16 +152,4 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg device.setType(type); return doPost("/api/device", device, Device.class); } - - protected CoapClient getCoapClient(FeatureType featureType) { - return new CoapClient(getFeatureTokenUrl(accessToken, featureType)); - } - - protected CoapClient getCoapClient(String featureTokenUrl) { - return new CoapClient(featureTokenUrl); - } - - protected String getFeatureTokenUrl(String token, FeatureType featureType) { - return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase(); - } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestCallback.java b/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestCallback.java new file mode 100644 index 0000000000..89fa13220d --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestCallback.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.transport.coap; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; + +import java.util.concurrent.CountDownLatch; + +@Slf4j +@Data +public class CoapTestCallback implements CoapHandler { + + protected final CountDownLatch latch; + protected Integer observe; + protected byte[] payloadBytes; + protected CoAP.ResponseCode responseCode; + + public CoapTestCallback() { + this.latch = new CountDownLatch(1); + } + + public CoapTestCallback(int subscribeCount) { + this.latch = new CountDownLatch(subscribeCount); + } + + public Integer getObserve() { + return observe; + } + + public byte[] getPayloadBytes() { + return payloadBytes; + } + + public CoAP.ResponseCode getResponseCode() { + return responseCode; + } + + @Override + public void onLoad(CoapResponse response) { + observe = response.getOptions().getObserve(); + payloadBytes = response.getPayload(); + responseCode = response.getCode(); + latch.countDown(); + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java b/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java new file mode 100644 index 0000000000..dc2a3f4c83 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/coap/CoapTestClient.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.coap; + +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapObserveRelation; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.thingsboard.server.common.msg.session.FeatureType; + +import java.io.IOException; + +public class CoapTestClient { + + private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/"; + private static final long CLIENT_REQUEST_TIMEOUT = 60000L; + + private final CoapClient client; + + public CoapTestClient(){ + this.client = createClient(); + } + + public CoapTestClient(String accessToken, FeatureType featureType) { + this.client = createClient(getFeatureTokenUrl(accessToken, featureType)); + } + + public CoapTestClient(String featureTokenUrl) { + this.client = createClient(featureTokenUrl); + } + + public void connectToCoap(String accessToken) { + setURI(accessToken, null); + } + + public void connectToCoap(String accessToken, FeatureType featureType) { + setURI(accessToken, featureType); + } + + public void disconnect() { + if (client != null) { + client.shutdown(); + } + } + + public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException { + return this.postMethod(requestBody.getBytes()); + } + + public CoapResponse postMethod(byte[] requestBodyBytes) throws ConnectorException, IOException { + return client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(requestBodyBytes, MediaTypeRegistry.APPLICATION_JSON); + } + + public void postMethod(CoapHandler handler, String payload, int format) { + client.post(handler, payload, format); + } + + public void postMethod(CoapHandler handler, byte[] payload, int format) { + client.post(handler, payload, format); + } + + public CoapResponse getMethod() throws ConnectorException, IOException { + return client.setTimeout(CLIENT_REQUEST_TIMEOUT).get(); + } + + public CoapObserveRelation getObserveRelation(CoapTestCallback callback){ + Request request = Request.newGet().setObserve(); + request.setType(CoAP.Type.CON); + return client.observe(request, callback); + } + + public void setURI(String featureTokenUrl) { + if (client == null) { + throw new RuntimeException("Failed to connect! CoapClient is not initialized!"); + } + client.setURI(featureTokenUrl); + } + + public void setURI(String accessToken, FeatureType featureType) { + if (featureType == null){ + featureType = FeatureType.ATTRIBUTES; + } + setURI(getFeatureTokenUrl(accessToken, featureType)); + } + + private CoapClient createClient() { + return new CoapClient(); + } + + private CoapClient createClient(String featureTokenUrl) { + return new CoapClient(featureTokenUrl); + } + + public static String getFeatureTokenUrl(String token, FeatureType featureType) { + return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase(); + } + + public static String getFeatureTokenUrl(String token, FeatureType featureType, int requestId) { + return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase() + "/" + requestId; + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java index 9d0078aa3f..b0b5c34d5a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java @@ -15,25 +15,95 @@ */ package org.thingsboard.server.transport.coap.attributes; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; +import org.awaitility.Awaitility; +import org.eclipse.californium.core.CoapObserveRelation; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DynamicProtoUtils; +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.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.SingleEntityFilter; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.transport.coap.CoapTestCallback; +import org.thingsboard.server.transport.coap.CoapTestClient; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.query.EntityKeyType.CLIENT_ATTRIBUTE; +import static org.thingsboard.server.common.data.query.EntityKeyType.SHARED_ATTRIBUTE; @Slf4j public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { - protected static final String POST_ATTRIBUTES_PAYLOAD = "{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73," + - "\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; + @Autowired + DefaultTransportService defaultTransportService; + + public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" + + "\n" + + "package test;\n" + + "\n" + + "message PostAttributes {\n" + + " string clientStr = 1;\n" + + " bool clientBool = 2;\n" + + " double clientDbl = 3;\n" + + " int32 clientLong = 4;\n" + + " JsonObject clientJson = 5;\n" + + "\n" + + " message JsonObject {\n" + + " int32 someNumber = 6;\n" + + " repeated int32 someArray = 7;\n" + + " NestedJsonObject someNestedObject = 8;\n" + + " message NestedJsonObject {\n" + + " string key = 9;\n" + + " }\n" + + " }\n" + + "}"; + + private static final String CLIENT_ATTRIBUTES_PAYLOAD = "{\"clientStr\":\"value1\",\"clientBool\":true,\"clientDbl\":42.0,\"clientLong\":73," + + "\"clientJson\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; + + private static final String SHARED_ATTRIBUTES_PAYLOAD = "{\"sharedStr\":\"value1\",\"sharedBool\":true,\"sharedDbl\":42.0,\"sharedLong\":73," + + "\"sharedJson\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}"; + + protected static final String SHARED_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION = "{\"sharedStr\":\"value\",\"sharedBool\":false,\"sharedDbl\":41.0,\"sharedLong\":72," + + "\"sharedJson\":{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}}"; - protected List getTsKvProtoList() { - TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value1", TransportProtos.KeyValueType.STRING_V); - TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "true", TransportProtos.KeyValueType.BOOLEAN_V); - TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "42.0", TransportProtos.KeyValueType.DOUBLE_V); - TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "73", TransportProtos.KeyValueType.LONG_V); - TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V); + private static final String SHARED_ATTRIBUTES_DELETED_RESPONSE = "{\"deleted\":[\"sharedJson\"]}"; + + private List getTsKvProtoList(String attributePrefix) { + TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto(attributePrefix + "Str", "value1", TransportProtos.KeyValueType.STRING_V); + TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto(attributePrefix + "Bool", "true", TransportProtos.KeyValueType.BOOLEAN_V); + TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto(attributePrefix + "Dbl", "42.0", TransportProtos.KeyValueType.DOUBLE_V); + TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto(attributePrefix + "Long", "73", TransportProtos.KeyValueType.LONG_V); + TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto(attributePrefix + "Json", "{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V); List tsKvProtoList = new ArrayList<>(); tsKvProtoList.add(tsKvProtoAttribute1); tsKvProtoList.add(tsKvProtoAttribute2); @@ -49,4 +119,305 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap tsKvProtoBuilder.setKv(keyValueProto); return tsKvProtoBuilder.build(); } + + private List getEntityKeys(List keys, EntityKeyType scope) { + return keys.stream().map(key -> new EntityKey(scope, key)).collect(Collectors.toList()); + } + + private byte[] getAttributesProtoPayloadBytes() { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema()); + DynamicSchema attributesSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + + DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); + Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); + assertNotNull(nestedJsonObjectBuilderDescriptor); + DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); + + DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); + Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); + assertNotNull(jsonObjectBuilderDescriptor); + DynamicMessage jsonObject = jsonObjectBuilder + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) + .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) + .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) + .build(); + + DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); + Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); + assertNotNull(postAttributesMsgDescriptor); + DynamicMessage postAttributesMsg = postAttributesBuilder + .setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1") + .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) + .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) + .build(); + return postAttributesMsg.toByteArray(); + } + + protected void processJsonTestRequestAttributesValuesFromTheServer() throws Exception { + client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); + SingleEntityFilter dtf = new SingleEntityFilter(); + dtf.setSingleEntity(savedDevice.getId()); + String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; + String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + List clientKeysList = List.of(clientKeysStr.split(",")); + List sharedKeysList = List.of(sharedKeysStr.split(",")); + List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); + List shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE); + List keys = new ArrayList<>(); + keys.addAll(csKeys); + keys.addAll(shKeys); + getWsClient().subscribeLatestUpdate(keys, dtf); + getWsClient().registerWaitForUpdate(2); + + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", + SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + + CoapResponse coapResponse = client.postMethod(CLIENT_ATTRIBUTES_PAYLOAD); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); + + String update = getWsClient().waitForUpdate(); + assertThat(update).as("ws update received").isNotBlank(); + + String featureTokenUrl = CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + clientKeysStr + "&sharedKeys=" + sharedKeysStr; + client.setURI(featureTokenUrl); + validateJsonResponse(client.getMethod()); + } + + protected void processProtoTestRequestAttributesValuesFromTheServer() throws Exception { + client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); + SingleEntityFilter dtf = new SingleEntityFilter(); + dtf.setSingleEntity(savedDevice.getId()); + String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson"; + String sharedKeysStr = "sharedStr,sharedBool,sharedDbl,sharedLong,sharedJson"; + List clientKeysList = List.of(clientKeysStr.split(",")); + List sharedKeysList = List.of(sharedKeysStr.split(",")); + List csKeys = getEntityKeys(clientKeysList, CLIENT_ATTRIBUTE); + List shKeys = getEntityKeys(sharedKeysList, SHARED_ATTRIBUTE); + List keys = new ArrayList<>(); + keys.addAll(csKeys); + keys.addAll(shKeys); + getWsClient().subscribeLatestUpdate(keys, dtf); + getWsClient().registerWaitForUpdate(2); + + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", + SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + + CoapResponse coapResponse = client.postMethod(getAttributesProtoPayloadBytes()); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); + + String update = getWsClient().waitForUpdate(); + assertThat(update).as("ws update received").isNotBlank(); + + String featureTokenUrl = CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + clientKeysStr + "&sharedKeys=" + sharedKeysStr; + client.setURI(featureTokenUrl); + validateProtoResponse(client.getMethod()); + } + + protected void processJsonTestSubscribeToAttributesUpdates(boolean emptyCurrentStateNotification) throws Exception { + if (!emptyCurrentStateNotification) { + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION, String.class, status().isOk()); + } + + client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); + CoapTestCallback callbackCoap = new CoapTestCallback(1); + + CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); + String awaitAlias = "await Json Test Subscribe To AttributesUpdates (client.getObserveRelation)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + 0 == callbackCoap.getObserve().intValue()); + if (emptyCurrentStateNotification) { + validateUpdateAttributesJsonResponse(callbackCoap, "{}"); + } else { + validateUpdateAttributesJsonResponse(callbackCoap, SHARED_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION); + } + + int expectedObserveForAttributesUpdate = callbackCoap.getObserve().intValue() + 1; + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + awaitAlias = "await Json Test Subscribe To AttributesUpdates (add attributes)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + expectedObserveForAttributesUpdate == callbackCoap.getObserve().intValue()); + validateUpdateAttributesJsonResponse(callbackCoap, SHARED_ATTRIBUTES_PAYLOAD); + + int expectedObserveForAttributesDelete = callbackCoap.getObserve().intValue() + 1; + doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class); + awaitAlias = "await Json Test Subscribe To AttributesUpdates (deleted attributes)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + expectedObserveForAttributesDelete == callbackCoap.getObserve().intValue()); + validateUpdateAttributesJsonResponse(callbackCoap, SHARED_ATTRIBUTES_DELETED_RESPONSE); + + observeRelation.proactiveCancel(); + assertTrue(observeRelation.isCanceled()); + + awaitClientAfterCancelObserve(); + } + + protected void processProtoTestSubscribeToAttributesUpdates(boolean emptyCurrentStateNotification) throws Exception { + if (!emptyCurrentStateNotification) { + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION, String.class, status().isOk()); + } + + client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); + CoapTestCallback callbackCoap = new CoapTestCallback(1); + + String awaitAlias = "await Proto Test Subscribe To Attributes Updates (add attributes)"; + CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + 0 == callbackCoap.getObserve().intValue()); + + if (emptyCurrentStateNotification) { + validateEmptyCurrentStateAttributesProtoResponse(callbackCoap); + } else { + validateCurrentStateAttributesProtoResponse(callbackCoap); + } + + int expectedObserveForAttributesUpdate = callbackCoap.getObserve().intValue() + 1; + doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); + awaitAlias = "await Proto Test Subscribe To Attributes Updates (add attributes)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + expectedObserveForAttributesUpdate == callbackCoap.getObserve().intValue()); + validateUpdateProtoAttributesResponse(callbackCoap, expectedObserveForAttributesUpdate); + + int expectedObserveForAttributesDelete = callbackCoap.getObserve().intValue() + 1; + doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class); + awaitAlias = "await Proto Test Subscribe To Attributes Updates (deleted attributes)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + expectedObserveForAttributesDelete == callbackCoap.getObserve().intValue()); + validateDeleteProtoAttributesResponse(callbackCoap, expectedObserveForAttributesDelete); + + observeRelation.proactiveCancel(); + assertTrue(observeRelation.isCanceled()); + + awaitClientAfterCancelObserve(); + } + + protected void validateJsonResponse(CoapResponse getAttributesResponse) throws InvalidProtocolBufferException { + assertEquals(CoAP.ResponseCode.CONTENT, getAttributesResponse.getCode()); + String expectedResponse = "{\"client\":" + CLIENT_ATTRIBUTES_PAYLOAD + ",\"shared\":" + SHARED_ATTRIBUTES_PAYLOAD + "}"; + assertEquals(JacksonUtil.toJsonNode(expectedResponse), JacksonUtil.fromBytes(getAttributesResponse.getPayload())); + } + + protected void validateProtoResponse(CoapResponse getAttributesResponse) throws InterruptedException, InvalidProtocolBufferException { + TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg(); + TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(getAttributesResponse.getPayload()); + assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId()); + List expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos)); + assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos)); + } + + protected void validateUpdateAttributesJsonResponse(CoapTestCallback callback, String expectedResponse) { + assertNotNull(callback.getPayloadBytes()); + String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); + assertEquals(JacksonUtil.toJsonNode(expectedResponse), JacksonUtil.toJsonNode(response)); + } + + protected void validateEmptyCurrentStateAttributesProtoResponse(CoapTestCallback callback) throws InvalidProtocolBufferException { + assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes()); + } + + protected void validateCurrentStateAttributesProtoResponse(CoapTestCallback callback) throws InvalidProtocolBufferException { + assertNotNull(callback.getPayloadBytes()); + TransportProtos.AttributeUpdateNotificationMsg.Builder expectedCurrentStateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); + TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("sharedStr", "value", TransportProtos.KeyValueType.STRING_V); + TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("sharedBool", "false", TransportProtos.KeyValueType.BOOLEAN_V); + TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("sharedDbl", "41.0", TransportProtos.KeyValueType.DOUBLE_V); + TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("sharedLong", "72", TransportProtos.KeyValueType.LONG_V); + TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("sharedJson", "{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V); + List tsKvProtoList = new ArrayList<>(); + tsKvProtoList.add(tsKvProtoAttribute1); + tsKvProtoList.add(tsKvProtoAttribute2); + tsKvProtoList.add(tsKvProtoAttribute3); + tsKvProtoList.add(tsKvProtoAttribute4); + tsKvProtoList.add(tsKvProtoAttribute5); + TransportProtos.AttributeUpdateNotificationMsg expectedCurrentStateNotificationMsg = expectedCurrentStateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList).build(); + TransportProtos.AttributeUpdateNotificationMsg actualCurrentStateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); + + List expectedSharedUpdatedList = expectedCurrentStateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List actualSharedUpdatedList = actualCurrentStateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + + assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size()); + assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList)); + } + + protected void validateUpdateProtoAttributesResponse(CoapTestCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { + assertNotNull(callback.getPayloadBytes()); + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); + List tsKvProtoList = getTsKvProtoList("shared"); + attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList); + + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); + TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); + + List actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + List expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); + + assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size()); + assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList)); + } + + protected void validateDeleteProtoAttributesResponse(CoapTestCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { + assertNotNull(callback.getPayloadBytes()); + TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); + attributeUpdateNotificationMsgBuilder.addSharedDeleted("sharedJson"); + + TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); + TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); + + assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size()); + assertEquals("sharedJson", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0)); + } + + private void awaitClientAfterCancelObserve() { + Awaitility.await("awaitClientAfterCancelObserve") + .pollInterval(10, TimeUnit.MILLISECONDS) + .atMost(5, TimeUnit.SECONDS) + .until(() -> { + log.trace("awaiting defaultTransportService.sessions is empty"); + return defaultTransportService.sessions.isEmpty(); + }); + } + + private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() { + TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder(); + List csTsKvProtoList = getTsKvProtoList("client"); + List shTsKvProtoList = getTsKvProtoList("shared"); + result.addAllClientAttributeList(csTsKvProtoList); + result.addAllSharedAttributeList(shTsKvProtoList); + result.setRequestId(0); + return result.build(); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestIntegrationTest.java index 977502e0e6..727fb52380 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestIntegrationTest.java @@ -15,34 +15,18 @@ */ package org.thingsboard.server.transport.coap.attributes.request; -import com.fasterxml.jackson.core.type.TypeReference; -import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.junit.After; import org.junit.Before; import org.junit.Test; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; -import org.thingsboard.server.common.msg.session.FeatureType; - -import java.nio.charset.StandardCharsets; -import java.util.List; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j @DaoSqlTest public class CoapAttributesRequestIntegrationTest extends AbstractCoapAttributesIntegrationTest { - protected static final long CLIENT_REQUEST_TIMEOUT = 60000L; - @Before public void beforeTest() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() @@ -58,44 +42,6 @@ public class CoapAttributesRequestIntegrationTest extends AbstractCoapAttributes @Test public void testRequestAttributesValuesFromTheServer() throws Exception { - processTestRequestAttributesValuesFromTheServer(); - } - - protected void processTestRequestAttributesValuesFromTheServer() throws Exception { - postAttributes(); - - long start = System.currentTimeMillis(); - long end = System.currentTimeMillis() + 5000; - - List savedAttributeKeys = null; - while (start <= end) { - savedAttributeKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); - if (savedAttributeKeys.size() == 5) { - break; - } - Thread.sleep(100); - start += 100; - } - assertNotNull(savedAttributeKeys); - - String keys = "attribute1,attribute2,attribute3,attribute4,attribute5"; - String featureTokenUrl = getFeatureTokenUrl(accessToken, FeatureType.ATTRIBUTES) + "?clientKeys=" + keys + "&sharedKeys=" + keys; - client = getCoapClient(featureTokenUrl); - - CoapResponse getAttributesResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).get(); - validateResponse(getAttributesResponse); - } - - protected void postAttributes() throws Exception { - doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); - client = getCoapClient(FeatureType.ATTRIBUTES); - CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(POST_ATTRIBUTES_PAYLOAD.getBytes(), MediaTypeRegistry.APPLICATION_JSON); - assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); - } - - protected void validateResponse(CoapResponse getAttributesResponse) throws InvalidProtocolBufferException { - assertEquals(CoAP.ResponseCode.CONTENT, getAttributesResponse.getCode()); - String expectedRequestPayload = "{\"client\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}},\"shared\":{\"attribute1\":\"value1\",\"attribute2\":true,\"attribute3\":42.0,\"attribute4\":73,\"attribute5\":{\"someNumber\":42,\"someArray\":[1,2,3],\"someNestedObject\":{\"key\":\"value\"}}}}"; - assertEquals(JacksonUtil.toJsonNode(expectedRequestPayload), JacksonUtil.toJsonNode(new String(getAttributesResponse.getPayload(), StandardCharsets.UTF_8))); + processJsonTestRequestAttributesValuesFromTheServer(); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestJsonIntegrationTest.java index e421fbf1a0..2c6928b8d4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestJsonIntegrationTest.java @@ -45,6 +45,6 @@ public class CoapAttributesRequestJsonIntegrationTest extends CoapAttributesRequ @Test public void testRequestAttributesValuesFromTheServer() throws Exception { - super.testRequestAttributesValuesFromTheServer(); + processJsonTestRequestAttributesValuesFromTheServer(); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestProtoIntegrationTest.java index cf3fd4c758..9244cd5b1f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/CoapAttributesRequestProtoIntegrationTest.java @@ -15,65 +15,18 @@ */ package org.thingsboard.server.transport.coap.attributes.request; -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; -import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; -import java.util.List; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - @Slf4j @DaoSqlTest public class CoapAttributesRequestProtoIntegrationTest extends CoapAttributesRequestIntegrationTest { - public static final String ATTRIBUTES_SCHEMA_STR = "syntax =\"proto3\";\n" + - "\n" + - "package test;\n" + - "\n" + - "message PostAttributes {\n" + - " string attribute1 = 1;\n" + - " bool attribute2 = 2;\n" + - " double attribute3 = 3;\n" + - " int32 attribute4 = 4;\n" + - " JsonObject attribute5 = 5;\n" + - "\n" + - " message JsonObject {\n" + - " int32 someNumber = 6;\n" + - " repeated int32 someArray = 7;\n" + - " NestedJsonObject someNestedObject = 8;\n" + - " message NestedJsonObject {\n" + - " string key = 9;\n" + - " }\n" + - " }\n" + - "}"; - @Before @Override public void beforeTest() throws Exception { @@ -88,74 +41,7 @@ public class CoapAttributesRequestProtoIntegrationTest extends CoapAttributesReq @Test public void testRequestAttributesValuesFromTheServer() throws Exception { - processTestRequestAttributesValuesFromTheServer(); - } - - protected void postAttributes() throws Exception { - doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); - CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; - CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); - assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); - DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); - assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema()); - DynamicSchema attributesSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); - - DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); - Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); - assertNotNull(nestedJsonObjectBuilderDescriptor); - DynamicMessage nestedJsonObject = nestedJsonObjectBuilder.setField(nestedJsonObjectBuilderDescriptor.findFieldByName("key"), "value").build(); - - DynamicMessage.Builder jsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject"); - Descriptors.Descriptor jsonObjectBuilderDescriptor = jsonObjectBuilder.getDescriptorForType(); - assertNotNull(jsonObjectBuilderDescriptor); - DynamicMessage jsonObject = jsonObjectBuilder - .setField(jsonObjectBuilderDescriptor.findFieldByName("someNumber"), 42) - .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 1) - .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 2) - .addRepeatedField(jsonObjectBuilderDescriptor.findFieldByName("someArray"), 3) - .setField(jsonObjectBuilderDescriptor.findFieldByName("someNestedObject"), nestedJsonObject) - .build(); - - DynamicMessage.Builder postAttributesBuilder = attributesSchema.newMessageBuilder("PostAttributes"); - Descriptors.Descriptor postAttributesMsgDescriptor = postAttributesBuilder.getDescriptorForType(); - assertNotNull(postAttributesMsgDescriptor); - DynamicMessage postAttributesMsg = postAttributesBuilder - .setField(postAttributesMsgDescriptor.findFieldByName("attribute1"), "value1") - .setField(postAttributesMsgDescriptor.findFieldByName("attribute2"), true) - .setField(postAttributesMsgDescriptor.findFieldByName("attribute3"), 42.0) - .setField(postAttributesMsgDescriptor.findFieldByName("attribute4"), 73) - .setField(postAttributesMsgDescriptor.findFieldByName("attribute5"), jsonObject) - .build(); - byte[] payload = postAttributesMsg.toByteArray(); - client = getCoapClient(FeatureType.ATTRIBUTES); - CoapResponse coapResponse = client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(payload, MediaTypeRegistry.APPLICATION_JSON); - assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); - } - - protected void validateResponse(CoapResponse getAttributesResponse) throws InvalidProtocolBufferException { - TransportProtos.GetAttributeResponseMsg expectedAttributesResponse = getExpectedAttributeResponseMsg(); - TransportProtos.GetAttributeResponseMsg actualAttributesResponse = TransportProtos.GetAttributeResponseMsg.parseFrom(getAttributesResponse.getPayload()); - assertEquals(expectedAttributesResponse.getRequestId(), actualAttributesResponse.getRequestId()); - List expectedClientKeyValueProtos = expectedAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - List expectedSharedKeyValueProtos = expectedAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - List actualClientKeyValueProtos = actualAttributesResponse.getClientAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - List actualSharedKeyValueProtos = actualAttributesResponse.getSharedAttributeListList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - assertTrue(actualClientKeyValueProtos.containsAll(expectedClientKeyValueProtos)); - assertTrue(actualSharedKeyValueProtos.containsAll(expectedSharedKeyValueProtos)); - } - - private TransportProtos.GetAttributeResponseMsg getExpectedAttributeResponseMsg() { - TransportProtos.GetAttributeResponseMsg.Builder result = TransportProtos.GetAttributeResponseMsg.newBuilder(); - List tsKvProtoList = getTsKvProtoList(); - result.addAllClientAttributeList(tsKvProtoList); - result.addAllSharedAttributeList(tsKvProtoList); - result.setRequestId(0); - return result.build(); + processProtoTestRequestAttributesValuesFromTheServer(); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java index bbbfad3c65..3214ae1639 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesIntegrationTest.java @@ -15,47 +15,25 @@ */ package org.thingsboard.server.transport.coap.attributes.updates; -import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.awaitility.Awaitility; -import org.eclipse.californium.core.CoapHandler; -import org.eclipse.californium.core.CoapObserveRelation; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.server.resources.Resource; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.coapserver.DefaultCoapServerService; -import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import org.thingsboard.server.transport.coap.CoapTransportResource; import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.spy; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Slf4j @DaoSqlTest public class CoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributesIntegrationTest { - private static final String RESPONSE_ATTRIBUTES_PAYLOAD_DELETED = "{\"deleted\":[\"attribute5\"]}"; - - protected static final String POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION = "{\"attribute1\":\"value\",\"attribute2\":false,\"attribute3\":41.0,\"attribute4\":72," + - "\"attribute5\":{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}}"; - CoapTransportResource coapTransportResource; @Autowired @@ -83,136 +61,11 @@ public class CoapAttributesUpdatesIntegrationTest extends AbstractCoapAttributes @Test public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { - processTestSubscribeToAttributesUpdates(false); + processJsonTestSubscribeToAttributesUpdates(false); } @Test public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { - processTestSubscribeToAttributesUpdates(true); - } - - protected void processTestSubscribeToAttributesUpdates(boolean emptyCurrentStateNotification) throws Exception { - if (!emptyCurrentStateNotification) { - doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION, String.class, status().isOk()); - } - client = getCoapClient(FeatureType.ATTRIBUTES); - - CountDownLatch latch = new CountDownLatch(1); - TestCoapCallback callback = new TestCoapCallback(latch); - - Request request = Request.newGet().setObserve(); - request.setType(CoAP.Type.CON); - CoapObserveRelation observeRelation = client.observe(request, callback); - - latch.await(3, TimeUnit.SECONDS); - - if (emptyCurrentStateNotification) { - validateEmptyCurrentStateAttributesResponse(callback); - } else { - validateCurrentStateAttributesResponse(callback); - } - - latch = new CountDownLatch(1); - int expectedObserveCnt = callback.getObserve().intValue() + 1; - doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk()); - latch.await(3, TimeUnit.SECONDS); - - validateUpdateAttributesResponse(callback, expectedObserveCnt); - - - latch = new CountDownLatch(1); - int expectedObserveBeforeDeleteCnt = callback.getObserve().intValue() + 1; - doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class); - latch.await(3, TimeUnit.SECONDS); - - validateDeleteAttributesResponse(callback, expectedObserveBeforeDeleteCnt); - observeRelation.proactiveCancel(); - assertTrue(observeRelation.isCanceled()); - - awaitClientAfterCancelObserve(); - } - - protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(0, callback.getObserve().intValue()); - String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); - assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION), JacksonUtil.toJsonNode(response)); - } - - protected void validateEmptyCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(0, callback.getObserve().intValue()); - String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); - assertEquals("{}", response); - } - - protected void validateUpdateAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(expectedObserveCnt, callback.getObserve().intValue()); - String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); - assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response)); - } - - protected void validateDeleteAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(expectedObserveCnt, callback.getObserve().intValue()); - String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8); - assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response)); - } - - protected static class TestCoapCallback implements CoapHandler { - - private final CountDownLatch latch; - - private Integer observe; - private byte[] payloadBytes; - private CoAP.ResponseCode responseCode; - - public Integer getObserve() { - return observe; - } - - public byte[] getPayloadBytes() { - return payloadBytes; - } - - public CoAP.ResponseCode getResponseCode() { - return responseCode; - } - - private TestCoapCallback(CountDownLatch latch) { - this.latch = latch; - } - - @Override - public void onLoad(CoapResponse response) { - observe = response.getOptions().getObserve(); - payloadBytes = response.getPayload(); - responseCode = response.getCode(); - latch.countDown(); - } - - @Override - public void onError() { - log.warn("Command Response Ack Error, No connect"); - } - - } - - private void awaitClientAfterCancelObserve() { - Awaitility.await("awaitClientAfterCancelObserve") - .pollInterval(10, TimeUnit.MILLISECONDS) - .atMost(5, TimeUnit.SECONDS) - .until(()->{ - log.trace("awaiting defaultTransportService.sessions is empty"); - return defaultTransportService.sessions.isEmpty();}); + processJsonTestSubscribeToAttributesUpdates(true); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java index 4ab052db46..816ffa53b3 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesJsonIntegrationTest.java @@ -23,10 +23,11 @@ import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; +import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; @Slf4j @DaoSqlTest -public class CoapAttributesUpdatesJsonIntegrationTest extends CoapAttributesUpdatesIntegrationTest { +public class CoapAttributesUpdatesJsonIntegrationTest extends AbstractCoapAttributesIntegrationTest { @Before public void beforeTest() throws Exception { @@ -45,11 +46,11 @@ public class CoapAttributesUpdatesJsonIntegrationTest extends CoapAttributesUpda @Test public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { - super.testSubscribeToAttributesUpdatesFromTheServer(); + processJsonTestSubscribeToAttributesUpdates(false); } @Test public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { - super.testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification(); + processJsonTestSubscribeToAttributesUpdates(true); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java index 4d9970b2dc..4a5e7c033f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/CoapAttributesUpdatesProtoIntegrationTest.java @@ -15,30 +15,19 @@ */ package org.thingsboard.server.transport.coap.attributes.updates; -import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.dao.service.DaoSqlTest; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; +import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest; @Slf4j @DaoSqlTest -public class CoapAttributesUpdatesProtoIntegrationTest extends CoapAttributesUpdatesIntegrationTest { +public class CoapAttributesUpdatesProtoIntegrationTest extends AbstractCoapAttributesIntegrationTest { @Before public void beforeTest() throws Exception { @@ -57,83 +46,11 @@ public class CoapAttributesUpdatesProtoIntegrationTest extends CoapAttributesUpd @Test public void testSubscribeToAttributesUpdatesFromTheServer() throws Exception { - processTestSubscribeToAttributesUpdates(false); + processProtoTestSubscribeToAttributesUpdates(false); } @Test public void testSubscribeToAttributesUpdatesFromTheServerWithEmptyCurrentStateNotification() throws Exception { - processTestSubscribeToAttributesUpdates(true); + processProtoTestSubscribeToAttributesUpdates(true); } - - protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(0, callback.getObserve().intValue()); - TransportProtos.AttributeUpdateNotificationMsg.Builder expectedCurrentStateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); - TransportProtos.TsKvProto tsKvProtoAttribute1 = getTsKvProto("attribute1", "value", TransportProtos.KeyValueType.STRING_V); - TransportProtos.TsKvProto tsKvProtoAttribute2 = getTsKvProto("attribute2", "false", TransportProtos.KeyValueType.BOOLEAN_V); - TransportProtos.TsKvProto tsKvProtoAttribute3 = getTsKvProto("attribute3", "41.0", TransportProtos.KeyValueType.DOUBLE_V); - TransportProtos.TsKvProto tsKvProtoAttribute4 = getTsKvProto("attribute4", "72", TransportProtos.KeyValueType.LONG_V); - TransportProtos.TsKvProto tsKvProtoAttribute5 = getTsKvProto("attribute5", "{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}", TransportProtos.KeyValueType.JSON_V); - List tsKvProtoList = new ArrayList<>(); - tsKvProtoList.add(tsKvProtoAttribute1); - tsKvProtoList.add(tsKvProtoAttribute2); - tsKvProtoList.add(tsKvProtoAttribute3); - tsKvProtoList.add(tsKvProtoAttribute4); - tsKvProtoList.add(tsKvProtoAttribute5); - TransportProtos.AttributeUpdateNotificationMsg expectedCurrentStateNotificationMsg = expectedCurrentStateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList).build(); - TransportProtos.AttributeUpdateNotificationMsg actualCurrentStateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); - - List expectedSharedUpdatedList = expectedCurrentStateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - List actualSharedUpdatedList = actualCurrentStateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - - assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size()); - assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList)); - - } - - protected void validateEmptyCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException { - assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(0, callback.getObserve().intValue()); - } - - protected void validateUpdateAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(expectedObserveCnt, callback.getObserve().intValue()); - TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); - List tsKvProtoList = getTsKvProtoList(); - attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList); - - TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); - TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); - - List actualSharedUpdatedList = actualAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - List expectedSharedUpdatedList = expectedAttributeUpdateNotificationMsg.getSharedUpdatedList().stream().map(TransportProtos.TsKvProto::getKv).collect(Collectors.toList()); - - assertEquals(expectedSharedUpdatedList.size(), actualSharedUpdatedList.size()); - assertTrue(actualSharedUpdatedList.containsAll(expectedSharedUpdatedList)); - - } - - protected void validateDeleteAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException { - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(expectedObserveCnt, callback.getObserve().intValue()); - TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder(); - attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5"); - - TransportProtos.AttributeUpdateNotificationMsg expectedAttributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build(); - TransportProtos.AttributeUpdateNotificationMsg actualAttributeUpdateNotificationMsg = TransportProtos.AttributeUpdateNotificationMsg.parseFrom(callback.getPayloadBytes()); - - assertEquals(expectedAttributeUpdateNotificationMsg.getSharedDeletedList().size(), actualAttributeUpdateNotificationMsg.getSharedDeletedList().size()); - assertEquals("attribute5", actualAttributeUpdateNotificationMsg.getSharedDeletedList().get(0)); - - } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimDeviceTest.java index de9c024bb8..e2be2280b2 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimDeviceTest.java @@ -16,10 +16,8 @@ package org.thingsboard.server.transport.coap.claim; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Before; @@ -34,6 +32,7 @@ import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.transport.coap.CoapTestClient; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; import java.io.IOException; @@ -96,7 +95,7 @@ public class CoapClaimDeviceTest extends AbstractCoapIntegrationTest { protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { log.warn("[testClaimingDevice] Device: {}, Transport type: {}", savedDevice.getName(), savedDevice.getType()); - client = getCoapClient(FeatureType.CLAIM); + client = new CoapTestClient(accessToken, FeatureType.CLAIM); byte[] payloadBytes; byte[] failurePayloadBytes; if (emptyPayload) { @@ -109,7 +108,7 @@ public class CoapClaimDeviceTest extends AbstractCoapIntegrationTest { validateClaimResponse(emptyPayload, client, payloadBytes, failurePayloadBytes); } - protected void validateClaimResponse(boolean emptyPayload, CoapClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception { + protected void validateClaimResponse(boolean emptyPayload, CoapTestClient client, byte[] payloadBytes, byte[] failurePayloadBytes) throws Exception { postClaimRequest(client, failurePayloadBytes); loginUser(customerAdmin.getName(), CUSTOMER_USER_PASSWORD); @@ -145,8 +144,8 @@ public class CoapClaimDeviceTest extends AbstractCoapIntegrationTest { assertEquals(claimResponse, ClaimResponse.CLAIMED); } - private void postClaimRequest(CoapClient client, byte[] payload) throws IOException, ConnectorException { - CoapResponse coapResponse = client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + private void postClaimRequest(CoapTestClient client, byte[] payload) throws IOException, ConnectorException { + CoapResponse coapResponse = client.postMethod(payload); assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimProtoDeviceTest.java index 812ea7392b..0096827af7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/claim/CoapClaimProtoDeviceTest.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportApiProtos; +import org.thingsboard.server.transport.coap.CoapTestClient; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @Slf4j @@ -56,7 +57,7 @@ public class CoapClaimProtoDeviceTest extends CoapClaimDeviceTest { @Override protected void processTestClaimingDevice(boolean emptyPayload) throws Exception { - client = getCoapClient(FeatureType.CLAIM); + client = new CoapTestClient(accessToken, FeatureType.CLAIM); byte[] payloadBytes; if (emptyPayload) { TransportApiProtos.ClaimDevice claimDevice = getClaimDevice(0, emptyPayload); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java index a952ef6d63..ce5ca909ba 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionJsonDeviceTest.java @@ -15,16 +15,13 @@ */ package org.thingsboard.server.transport.coap.provision; -import com.google.gson.JsonObject; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import org.thingsboard.server.common.data.CoapDeviceType; @@ -34,14 +31,12 @@ import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.session.FeatureType; -import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionResponseStatus; +import org.thingsboard.server.transport.coap.CoapTestClient; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; -import java.io.IOException; - @Slf4j @DaoSqlTest public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { @@ -95,10 +90,11 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { .transportPayloadType(TransportPayloadType.JSON) .build(); processBeforeTest(configProperties); - byte[] result = createCoapClientAndPublish().getPayload(); - JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish()); + Assert.assertTrue(response.hasNonNull("errorMsg")); + Assert.assertTrue(response.hasNonNull("status")); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").asText()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").asText()); } @@ -112,8 +108,9 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - byte[] result = createCoapClientAndPublish().getPayload(); - JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish()); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); @@ -121,8 +118,8 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); } @@ -137,8 +134,9 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { .build(); processBeforeTest(configProperties); String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; - byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); - JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish(requestCredentials)); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); @@ -146,10 +144,10 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "ACCESS_TOKEN"); Assert.assertEquals(deviceCredentials.getCredentialsId(), "test_token"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); } @@ -164,8 +162,9 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { .build(); processBeforeTest(configProperties); String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; - byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); - JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish(requestCredentials)); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); @@ -173,7 +172,7 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); Assert.assertEquals(deviceCredentials.getCredentialsType().name(), "X509_CERTIFICATE"); String cert = EncryptionUtil.certTrimNewLines(deviceCredentials.getCredentialsValue()); @@ -182,7 +181,7 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { Assert.assertEquals(deviceCredentials.getCredentialsId(), sha3Hash); Assert.assertEquals(deviceCredentials.getCredentialsValue(), "testHash"); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); } private void processTestProvisioningCheckPreProvisionedDevice() throws Exception { @@ -195,13 +194,14 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - byte[] result = createCoapClientAndPublish().getPayload(); - JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish()); + Assert.assertTrue(response.hasNonNull("credentialsType")); + Assert.assertTrue(response.hasNonNull("status")); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").getAsString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.get("credentialsType").asText()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.get("status").asText()); } private void processTestProvisioningWithBadKeyDevice() throws Exception { @@ -214,25 +214,21 @@ public class CoapProvisionJsonDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - byte[] result = createCoapClientAndPublish().getPayload(); - JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); - Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").getAsString()); + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish()); + Assert.assertTrue(response.hasNonNull("errorMsg")); + Assert.assertTrue(response.hasNonNull("status")); + Assert.assertEquals("Provision data was not found!", response.get("errorMsg").asText()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.get("status").asText()); } - private CoapResponse createCoapClientAndPublish() throws Exception { + private byte[] createCoapClientAndPublish() throws Exception { return createCoapClientAndPublish(""); } - private CoapResponse createCoapClientAndPublish(String deviceCredentials) throws Exception { + private byte[] createCoapClientAndPublish(String deviceCredentials) throws Exception { String provisionRequestMsg = createTestProvisionMessage(deviceCredentials); - client = getCoapClient(FeatureType.PROVISION); - return postProvision(client, provisionRequestMsg.getBytes()); - } - - - private CoapResponse postProvision(CoapClient client, byte[] payload) throws IOException, ConnectorException { - return client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + client = new CoapTestClient(accessToken, FeatureType.PROVISION); + return client.postMethod(provisionRequestMsg.getBytes()).getPayload(); } private String createTestProvisionMessage(String deviceCredentials) { diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java index a2e270796e..a5253b8409 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/CoapProvisionProtoDeviceTest.java @@ -16,10 +16,7 @@ package org.thingsboard.server.transport.coap.provision; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Assert; import org.junit.Test; @@ -44,10 +41,9 @@ import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceReque import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.transport.coap.CoapTestClient; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; -import java.io.IOException; - @Slf4j @DaoSqlTest public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { @@ -101,9 +97,9 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { .transportPayloadType(TransportPayloadType.PROTOBUF) .build(); processBeforeTest(configProperties); - ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); + ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish()); Assert.assertNotNull(result); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().name()); } private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { @@ -116,7 +112,7 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish()); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); @@ -124,8 +120,8 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, createdDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); } private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { @@ -138,9 +134,12 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder() + .setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()) + .build(); - ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayload()); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom( + createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials))); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); @@ -164,9 +163,13 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); + CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder() + .setValidateDeviceX509CertRequestMsg( + ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()) + .build(); - ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayload()); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom( + createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials))); Device createdDevice = deviceService.findDeviceByTenantIdAndName(tenantId, "Test Provision device"); @@ -196,12 +199,12 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); - Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().toString()); - Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().toString()); + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), response.getCredentialsType().name()); + Assert.assertEquals(ProvisionResponseStatus.SUCCESS.name(), response.getStatus().name()); } private void processTestProvisioningWithBadKeyDevice() throws Exception { @@ -214,24 +217,19 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { .provisionSecret("testProvisionSecret") .build(); processBeforeTest(configProperties); - ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); - Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); - } - - private CoapResponse createCoapClientAndPublish() throws Exception { - byte[] provisionRequestMsg = createTestProvisionMessage(); - CoapResponse coapResponse = createCoapClientAndPublish(provisionRequestMsg); - Assert.assertNotNull("COAP response", coapResponse); - return coapResponse; + ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish()); + Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().name()); } - private CoapResponse createCoapClientAndPublish(byte[] provisionRequestMsg) throws Exception { - client = getCoapClient(FeatureType.PROVISION); - return postProvision(client, provisionRequestMsg); + private byte[] createCoapClientAndPublish() throws Exception { + return createCoapClientAndPublish(createTestProvisionMessage()); } - private CoapResponse postProvision(CoapClient client, byte[] payload) throws IOException, ConnectorException { - return client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); + private byte[] createCoapClientAndPublish(byte[] provisionRequestMsg) throws Exception { + client = new CoapTestClient(accessToken, FeatureType.PROVISION); + CoapResponse coapResponse = client.postMethod(provisionRequestMsg); + Assert.assertNotNull("COAP response", coapResponse); + return coapResponse.getPayload(); } private byte[] createTestsProvisionMessage(CredentialsType credentialsType, CredentialsDataProto credentialsData) throws Exception { @@ -247,7 +245,6 @@ public class CoapProvisionProtoDeviceTest extends AbstractCoapIntegrationTest { .toByteArray(); } - private byte[] createTestProvisionMessage() throws Exception { return createTestsProvisionMessage(null, null); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index 5262b55b97..824097e071 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -16,22 +16,35 @@ package org.thingsboard.server.transport.coap.rpc; import com.fasterxml.jackson.databind.JsonNode; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapHandler; import org.eclipse.californium.core.CoapObserveRelation; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.core.coap.Request; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DynamicProtoUtils; +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.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.transport.coap.CoapTestCallback; +import org.thingsboard.server.transport.coap.CoapTestClient; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -41,76 +54,96 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @Slf4j public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractCoapIntegrationTest { + public static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "package rpc;\n" + + "\n" + + "message RpcRequestMsg {\n" + + " optional string method = 1;\n" + + " optional int32 requestId = 2;\n" + + " Params params = 3;\n" + + "\n" + + " message Params {\n" + + " optional string pin = 1;\n" + + " optional int32 value = 2;\n" + + " }\n" + + "}"; + protected static final String DEVICE_RESPONSE = "{\"value1\":\"A\",\"value2\":\"B\"}"; protected static final Long asyncContextTimeoutToUseRpcPlugin = 10000L; - protected void processOneWayRpcTest() throws Exception { - client = getCoapClient(FeatureType.RPC); - client.useCONs(); - - CountDownLatch latch = new CountDownLatch(1); - TestCoapCallback callback = new TestCoapCallback(client, latch, true); - - Request request = Request.newGet().setObserve(); - CoapObserveRelation observeRelation = client.observe(request, callback); - - latch.await(3, TimeUnit.SECONDS); - - validateCurrentStateNotification(callback); - - latch = new CountDownLatch(1); - + protected void processOneWayRpcTest(boolean protobuf) throws Exception { + client = new CoapTestClient(accessToken, FeatureType.RPC); + CoapTestCallback callbackCoap = new TestCoapCallbackForRPC(client, 1, true, protobuf); + + CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); + String awaitAlias = "await One Way Rpc (client.getObserveRelation)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.VALID.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + 0 == callbackCoap.getObserve().intValue()); + validateCurrentStateNotification(callbackCoap); + int expectedObserveCountAfterGpioRequest = callbackCoap.getObserve().intValue() + 1; String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); String result = doPostAsync("/api/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isOk()); - - latch.await(3, TimeUnit.SECONDS); - - validateOneWayStateChangedNotification(callback, result); + awaitAlias = "await One Way Rpc setGpio(method, params, value)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + expectedObserveCountAfterGpioRequest == callbackCoap.getObserve().intValue()); + validateOneWayStateChangedNotification(callbackCoap, result); observeRelation.proactiveCancel(); assertTrue(observeRelation.isCanceled()); } - protected void processTwoWayRpcTest(String expectedResponseResult) throws Exception { - client = getCoapClient(FeatureType.RPC); - client.useCONs(); - - CountDownLatch latch = new CountDownLatch(1); - TestCoapCallback callback = new TestCoapCallback(client, latch, false); + protected void processTwoWayRpcTest(String expectedResponseResult, boolean protobuf) throws Exception { + client = new CoapTestClient(accessToken, FeatureType.RPC); + CoapTestCallback callbackCoap = new TestCoapCallbackForRPC(client, 1, false, protobuf); - Request request = Request.newGet().setObserve(); - request.setType(CoAP.Type.CON); - CoapObserveRelation observeRelation = client.observe(request, callback); - - latch.await(3, TimeUnit.SECONDS); - - validateCurrentStateNotification(callback); + CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); + String awaitAlias = "await Two Way Rpc (client.getObserveRelation)"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.VALID.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + 0 == callbackCoap.getObserve().intValue()); + validateCurrentStateNotification(callbackCoap); String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - + int expectedObserveCountAfterGpioRequest1 = callbackCoap.getObserve().intValue() + 1; String actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); - latch.await(3, TimeUnit.SECONDS); - - validateTwoWayStateChangedNotification(callback, 1, expectedResponseResult, actualResult); - - latch = new CountDownLatch(1); - + awaitAlias = "await Two Way Rpc (setGpio(method, params, value) first"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + expectedObserveCountAfterGpioRequest1 == callbackCoap.getObserve().intValue()); + validateTwoWayStateChangedNotification(callbackCoap, expectedResponseResult, actualResult); + + int expectedObserveCountAfterGpioRequest2 = callbackCoap.getObserve().intValue() + 1; actualResult = doPostAsync("/api/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); - latch.await(3, TimeUnit.SECONDS); + awaitAlias = "await Two Way Rpc (setGpio(method, params, value) first"; + await(awaitAlias) + .atMost(10, TimeUnit.SECONDS) + .until(() -> CoAP.ResponseCode.CONTENT.equals(callbackCoap.getResponseCode()) && + callbackCoap.getObserve() != null && + expectedObserveCountAfterGpioRequest2 == callbackCoap.getObserve().intValue()); - validateTwoWayStateChangedNotification(callback, 2, expectedResponseResult, actualResult); + validateTwoWayStateChangedNotification(callbackCoap, expectedResponseResult, actualResult); observeRelation.proactiveCancel(); assertTrue(observeRelation.isCanceled()); } - protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) { + protected void processOnLoadResponse(CoapResponse response, CoapTestClient client, Integer observe, CountDownLatch latch) { JsonNode responseJson = JacksonUtil.fromBytes(response.getPayload()); - client.setURI(getRpcResponseFeatureTokenUrl(accessToken, responseJson.get("id").asInt())); - client.post(new CoapHandler() { + client.setURI(CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.RPC, responseJson.get("id").asInt())); + client.postMethod(new CoapHandler() { @Override public void onLoad(CoapResponse response) { log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText()); @@ -124,36 +157,80 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC }, DEVICE_RESPONSE, MediaTypeRegistry.APPLICATION_JSON); } - protected String getRpcResponseFeatureTokenUrl(String token, int requestId) { - return COAP_BASE_URL + token + "/" + FeatureType.RPC.name().toLowerCase() + "/" + requestId; + protected void processOnLoadProtoResponse(CoapResponse response, CoapTestClient client, Integer observe, CountDownLatch latch) { + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); + ProtoFileElement rpcRequestProtoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema()); + DynamicSchema rpcRequestProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcRequestProtoFileElement, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); + + byte[] requestPayload = response.getPayload(); + DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg"); + Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType(); + try { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload); + Descriptors.FieldDescriptor requestIdDescriptor = rpcRequestMsgDescriptor.findFieldByName("requestId"); + int requestId = (int) dynamicMessage.getField(requestIdDescriptor); + ProtoFileElement rpcResponseProtoSchemaFile = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema()); + DynamicSchema rpcResponseProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); + DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg"); + Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType(); + DynamicMessage rpcResponseMsg = rpcResponseBuilder + .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE) + .build(); + client.setURI(CoapTestClient.getFeatureTokenUrl(accessToken, FeatureType.RPC, requestId)); + client.postMethod(new CoapHandler() { + @Override + public void onLoad(CoapResponse response) { + log.warn("Command Response Ack: {}", response.getCode()); + latch.countDown(); + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + }, rpcResponseMsg.toByteArray(), MediaTypeRegistry.APPLICATION_JSON); + } catch (InvalidProtocolBufferException e) { + log.warn("Command Response Ack Error, Invalid response received: ", e); + } } - protected class TestCoapCallback implements CoapHandler { + private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + } - private final CoapClient client; - private final CountDownLatch latch; - private final boolean isOneWayRpc; + private void validateCurrentStateNotification(CoapTestCallback callback) { + assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes()); + } - private Integer observe; - private byte[] payloadBytes; - private CoAP.ResponseCode responseCode; + private void validateOneWayStateChangedNotification(CoapTestCallback callback, String result) { + assertTrue(StringUtils.isEmpty(result)); + assertNotNull(callback.getPayloadBytes()); + } - public Integer getObserve() { - return observe; - } + private void validateTwoWayStateChangedNotification(CoapTestCallback callback, String expectedResult, String actualResult) { + assertEquals(expectedResult, actualResult); + assertNotNull(callback.getPayloadBytes()); + } - public byte[] getPayloadBytes() { - return payloadBytes; - } + protected class TestCoapCallbackForRPC extends CoapTestCallback { - public CoAP.ResponseCode getResponseCode() { - return responseCode; - } + private final CoapTestClient client; + private final boolean isOneWayRpc; + private final boolean protobuf; - TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) { + TestCoapCallbackForRPC(CoapTestClient client, int subscribeCount, boolean isOneWayRpc, boolean protobuf) { + super(subscribeCount); this.client = client; - this.latch = latch; this.isOneWayRpc = isOneWayRpc; + this.protobuf = protobuf; } @Override @@ -163,7 +240,11 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC observe = response.getOptions().getObserve(); if (observe != null) { if (!isOneWayRpc && observe > 0) { - processOnLoadResponse(response, client, observe, latch); + if (!protobuf){ + processOnLoadResponse(response, client, observe, latch); + } else { + processOnLoadProtoResponse(response, client, observe, latch); + } } else { latch.countDown(); } @@ -174,31 +255,5 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC public void onError() { log.warn("Command Response Ack Error, No connect"); } - - } - - private void validateCurrentStateNotification(TestCoapCallback callback) { - assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(callback.getResponseCode(), CoAP.ResponseCode.VALID); - assertEquals(0, callback.getObserve().intValue()); - } - - private void validateOneWayStateChangedNotification(TestCoapCallback callback, String result) { - assertTrue(StringUtils.isEmpty(result)); - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(1, callback.getObserve().intValue()); } - - private void validateTwoWayStateChangedNotification(TestCoapCallback callback, int expectedObserveNumber, String expectedResult, String actualResult) { - assertEquals(expectedResult, actualResult); - assertNotNull(callback.getPayloadBytes()); - assertNotNull(callback.getObserve()); - assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode()); - assertEquals(expectedObserveNumber, callback.getObserve().intValue()); - } - - } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java index cd0e9268b4..d2ef37f795 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcDefaultIntegrationTest.java @@ -21,9 +21,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.CoapDeviceType; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.service.security.AccessValidator; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; @@ -87,12 +84,12 @@ public class CoapServerSideRpcDefaultIntegrationTest extends AbstractCoapServerS @Test public void testServerCoapOneWayRpc() throws Exception { - processOneWayRpcTest(); + processOneWayRpcTest(false); } @Test public void testServerCoapTwoWayRpc() throws Exception { - processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}"); + processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java index 6dde4b919b..29fe4faf70 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcJsonIntegrationTest.java @@ -45,12 +45,11 @@ public class CoapServerSideRpcJsonIntegrationTest extends AbstractCoapServerSide @Test public void testServerCoapOneWayRpc() throws Exception { - processOneWayRpcTest(); + processOneWayRpcTest(false); } @Test public void testServerCoapTwoWayRpc() throws Exception { - processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}"); + processTwoWayRpcTest("{\"value1\":\"A\",\"value2\":\"B\"}", false); } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java index 424f5bf424..93b5c6c6fb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/CoapServerSideRpcProtoIntegrationTest.java @@ -15,54 +15,19 @@ */ package org.thingsboard.server.transport.coap.rpc; -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; -import org.eclipse.californium.core.CoapHandler; -import org.eclipse.californium.core.CoapResponse; -import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; -import java.util.concurrent.CountDownLatch; - -import static org.junit.Assert.assertTrue; - @Slf4j @DaoSqlTest public class CoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { - private static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + - "package rpc;\n" + - "\n" + - "message RpcRequestMsg {\n" + - " optional string method = 1;\n" + - " optional int32 requestId = 2;\n" + - " Params params = 3;\n" + - "\n" + - " message Params {\n" + - " optional string pin = 1;\n" + - " optional int32 value = 2;\n" + - " }\n" + - "}"; - @Before public void beforeTest() throws Exception { CoapTestConfigProperties configProperties = CoapTestConfigProperties.builder() @@ -81,61 +46,11 @@ public class CoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSid @Test public void testServerCoapOneWayRpc() throws Exception { - processOneWayRpcTest(); + processOneWayRpcTest(true); } @Test public void testServerCoapTwoWayRpc() throws Exception { - processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}"); - } - - @Override - protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) { - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); - ProtoFileElement rpcRequestProtoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema()); - DynamicSchema rpcRequestProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcRequestProtoFileElement, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); - - byte[] requestPayload = response.getPayload(); - DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg"); - Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType(); - try { - DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload); - Descriptors.FieldDescriptor requestIdDescriptor = rpcRequestMsgDescriptor.findFieldByName("requestId"); - int requestId = (int) dynamicMessage.getField(requestIdDescriptor); - ProtoFileElement rpcResponseProtoSchemaFile = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema()); - DynamicSchema rpcResponseProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); - DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg"); - Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType(); - DynamicMessage rpcResponseMsg = rpcResponseBuilder - .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE) - .build(); - client.setURI(getRpcResponseFeatureTokenUrl(accessToken, requestId)); - client.post(new CoapHandler() { - @Override - public void onLoad(CoapResponse response) { - log.warn("Command Response Ack: {}", response.getCode()); - latch.countDown(); - } - - @Override - public void onError() { - log.warn("Command Response Ack Error, No connect"); - } - }, rpcResponseMsg.toByteArray(), MediaTypeRegistry.APPLICATION_JSON); - } catch (InvalidProtocolBufferException e) { - log.warn("Command Response Ack Error, Invalid response received: ", e); - } - } - - private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() { - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); - CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; - CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); - assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); - DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); - assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); - return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + processTwoWayRpcTest("{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}", true); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java index ee566be2bb..057e4366de 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesIntegrationTest.java @@ -17,11 +17,8 @@ package org.thingsboard.server.transport.coap.telemetry.attributes; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -29,9 +26,9 @@ import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.transport.coap.CoapTestClient; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; -import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashMap; @@ -74,30 +71,17 @@ public class CoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { } protected void processAttributesTest(List expectedKeys, byte[] payload, boolean presenceFieldsTest) throws Exception { - client = getCoapClient(FeatureType.ATTRIBUTES); - postAttributes(client, payload); + client = new CoapTestClient(accessToken, FeatureType.ATTRIBUTES); + CoapResponse coapResponse = client.postMethod(payload); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); DeviceId deviceId = savedDevice.getId(); - - long start = System.currentTimeMillis(); - long end = System.currentTimeMillis() + 5000; - - List actualKeys = null; - while (start <= end) { - actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); - if (actualKeys.size() == expectedKeys.size()) { - break; - } - Thread.sleep(100); - start += 100; - } + List actualKeys = getActualKeysList(deviceId, expectedKeys); assertNotNull(actualKeys); Set actualKeySet = new HashSet<>(actualKeys); - Set expectedKeySet = new HashSet<>(expectedKeys); - assertEquals(expectedKeySet, actualKeySet); String getAttributesValuesUrl = getAttributesValuesUrl(deviceId, actualKeySet); @@ -111,14 +95,6 @@ public class CoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { doDelete(deleteAttributesUrl); } - private void postAttributes(CoapClient client, byte[] payload) throws IOException, ConnectorException { - if (payload == null) { - payload = PAYLOAD_VALUES_STR.getBytes(); - } - CoapResponse coapResponse = client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); - assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); - } - @SuppressWarnings({"unchecked", "rawtypes"}) protected void assertAttributesValues(List> deviceValues, Set keySet) { for (Map map : deviceValues) { @@ -171,6 +147,22 @@ public class CoapAttributesIntegrationTest extends AbstractCoapIntegrationTest { } } + private List getActualKeysList(DeviceId deviceId, List expectedKeys) throws Exception { + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis() + 5000; + + List actualKeys = null; + while (start <= end) { + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/attributes/CLIENT_SCOPE", new TypeReference<>() {}); + if (actualKeys.size() == expectedKeys.size()) { + break; + } + Thread.sleep(100); + start += 100; + } + return actualKeys; + } + private String getAttributesValuesUrl(DeviceId deviceId, Set actualKeySet) { return "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/attributes/CLIENT_SCOPE?keys=" + String.join(",", actualKeySet); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java index 0c7ab4d1c5..a1fba145a9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesIntegrationTest.java @@ -17,19 +17,17 @@ package org.thingsboard.server.transport.coap.telemetry.timeseries; import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.MediaTypeRegistry; -import org.eclipse.californium.elements.exception.ConnectorException; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest; +import org.thingsboard.server.transport.coap.CoapTestClient; import org.thingsboard.server.transport.coap.CoapTestConfigProperties; -import java.io.IOException; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -75,28 +73,16 @@ public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoap } protected void processTestPostTelemetry(byte[] payloadBytes, List expectedKeys, boolean withTs, boolean presenceFieldsTest) throws Exception { - client = getCoapClient(FeatureType.TELEMETRY); - postTelemetry(client, payloadBytes); - - String deviceId = savedDevice.getId().getId().toString(); - - long start = System.currentTimeMillis(); - long end = System.currentTimeMillis() + 5000; + client = new CoapTestClient(accessToken, FeatureType.TELEMETRY); + CoapResponse coapResponse = client.postMethod(payloadBytes); + assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); - List actualKeys = null; - while (start <= end) { - actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() {}); - if (actualKeys.size() == expectedKeys.size()) { - break; - } - Thread.sleep(100); - start += 100; - } + DeviceId deviceId = savedDevice.getId(); + List actualKeys = getActualKeysList(deviceId, expectedKeys); assertNotNull(actualKeys); Set actualKeySet = new HashSet<>(actualKeys); Set expectedKeySet = new HashSet<>(expectedKeys); - assertEquals(expectedKeySet, actualKeySet); String getTelemetryValuesUrl; @@ -105,8 +91,8 @@ public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoap } else { getTelemetryValuesUrl = "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + String.join(",", actualKeySet); } - start = System.currentTimeMillis(); - end = System.currentTimeMillis() + 5000; + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis() + 5000; Map>> values = null; while (start <= end) { values = doGetAsyncTyped(getTelemetryValuesUrl, new TypeReference<>() {}); @@ -144,11 +130,6 @@ public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoap } } - private void postTelemetry(CoapClient client, byte[] payload) throws IOException, ConnectorException { - CoapResponse coapResponse = client.setTimeout((long) 60000).post(payload, MediaTypeRegistry.APPLICATION_JSON); - assertEquals(CoAP.ResponseCode.CREATED, coapResponse.getCode()); - } - private void assertTs(Map>> deviceValues, List expectedKeys, int ts, int arrayIndex) { assertEquals(ts, deviceValues.get(expectedKeys.get(0)).get(arrayIndex).get("ts")); assertEquals(ts, deviceValues.get(expectedKeys.get(1)).get(arrayIndex).get("ts")); @@ -207,4 +188,20 @@ public abstract class AbstractCoapTimeseriesIntegrationTest extends AbstractCoap } } + private List getActualKeysList(DeviceId deviceId, List expectedKeys) throws Exception { + long start = System.currentTimeMillis(); + long end = System.currentTimeMillis() + 5000; + + List actualKeys = null; + while (start <= end) { + actualKeys = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/keys/timeseries", new TypeReference<>() {}); + if (actualKeys.size() == expectedKeys.size()) { + break; + } + Thread.sleep(100); + start += 100; + } + return actualKeys; + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index e357b23afe..6547405337 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -196,7 +196,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M device.getId().getId().toString(); lwM2MTestClient.start(isStartLw); await(awaitAlias) - .atMost(1000, TimeUnit.MILLISECONDS) + .atMost(20, TimeUnit.SECONDS) .until(() -> finishState.equals(lwM2MTestClient.getClientState())); Assert.assertEquals(expectedStatuses, lwM2MTestClient.getClientStates()); } @@ -234,7 +234,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M String deviceId = device.getId().getId().toString(); lwM2MTestClient.start(true); await(awaitAlias) - .atMost(1000, TimeUnit.MILLISECONDS) + .atMost(20, TimeUnit.SECONDS) .until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState())); Assert.assertEquals(expectedStatusesLwm2m, lwM2MTestClient.getClientStates()); @@ -246,7 +246,7 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M expectedStatusesBs.add(ON_DEREGISTRATION_STARTED); expectedStatusesBs.add(ON_DEREGISTRATION_SUCCESS); await(awaitAlias) - .atMost(1000, TimeUnit.MILLISECONDS) + .atMost(20, TimeUnit.SECONDS) .until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState())); Assert.assertEquals(expectedStatusesBs, lwM2MTestClient.getClientStates()); } diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 518c9b42d3..279d1e99be 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -55,3 +55,5 @@ queue.rule-engine.queues[2].partitions=2 queue.rule-engine.queues[2].processing-strategy.retries=1 queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0 queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0 + +usage.stats.report.enabled=false \ No newline at end of file diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java index ed39bf3716..ce16bcc00f 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java @@ -21,26 +21,34 @@ import org.springframework.cache.support.NullValue; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisStringCommands; +import org.springframework.data.redis.connection.jedis.JedisClusterConnection; +import org.springframework.data.redis.connection.jedis.JedisConnection; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.core.types.Expiration; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.util.JedisClusterCRC16; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Optional; import java.util.concurrent.TimeUnit; @Slf4j public abstract class RedisTbTransactionalCache implements TbTransactionalCache { private static final byte[] BINARY_NULL_VALUE = RedisSerializer.java().serialize(NullValue.INSTANCE); + static final JedisPool MOCK_POOL = new JedisPool(); //non-null pool required for JedisConnection to trigger closing jedis connection @Getter private final String cacheName; - private final RedisConnectionFactory connectionFactory; - private final RedisSerializer keySerializer = new StringRedisSerializer(); - private final RedisSerializer valueSerializer; + private final JedisConnectionFactory connectionFactory; + private final RedisSerializer keySerializer = StringRedisSerializer.UTF_8; + private final TbRedisSerializer valueSerializer; private final Expiration evictExpiration; private final Expiration cacheTtl; @@ -48,17 +56,17 @@ public abstract class RedisTbTransactionalCache valueSerializer) { + TbRedisSerializer valueSerializer) { this.cacheName = cacheName; - this.connectionFactory = connectionFactory; + this.connectionFactory = (JedisConnectionFactory) connectionFactory; this.valueSerializer = valueSerializer; this.evictExpiration = Expiration.from(configuration.getEvictTtlInMs(), TimeUnit.MILLISECONDS); - if (cacheSpecsMap.getSpecs() != null && cacheSpecsMap.getSpecs().get(cacheName) != null) { - CacheSpecs cacheSpecs = cacheSpecsMap.getSpecs().get(cacheName); - this.cacheTtl = Expiration.from(cacheSpecs.getTimeToLiveInMinutes(), TimeUnit.MINUTES); - } else { - this.cacheTtl = Expiration.persistent(); - } + this.cacheTtl = Optional.ofNullable(cacheSpecsMap) + .map(CacheSpecsMap::getSpecs) + .map(x -> x.get(cacheName)) + .map(CacheSpecs::getTimeToLiveInMinutes) + .map(t -> Expiration.from(t, TimeUnit.MINUTES)) + .orElseGet(Expiration::persistent); } @Override @@ -71,7 +79,7 @@ public abstract class RedisTbTransactionalCache(this, connection); } + private RedisConnection getConnection(byte[] rawKey) { + if (!connectionFactory.isRedisClusterAware()) { + return connectionFactory.getConnection(); + } + RedisConnection connection = connectionFactory.getClusterConnection(); + + int slotNum = JedisClusterCRC16.getSlot(rawKey); + Jedis jedis = ((JedisClusterConnection) connection).getNativeConnection().getConnectionFromSlot(slotNum); + + JedisConnection jedisConnection = new JedisConnection(jedis, MOCK_POOL, jedis.getDB()); + jedisConnection.setConvertPipelineAndTxResults(connectionFactory.getConvertPipelineAndTxResults()); + + return jedisConnection; + } + private RedisConnection watch(byte[][] rawKeysList) { - var connection = connectionFactory.getConnection(); + RedisConnection connection = getConnection(rawKeysList[0]); try { connection.watch(rawKeysList); connection.multi(); diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/SimpleTbCacheValueWrapper.java b/common/cache/src/main/java/org/thingsboard/server/cache/SimpleTbCacheValueWrapper.java index f5607fb760..58ccfd8fb9 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/SimpleTbCacheValueWrapper.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/SimpleTbCacheValueWrapper.java @@ -17,8 +17,10 @@ package org.thingsboard.server.cache; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import lombok.ToString; import org.springframework.cache.Cache; +@ToString @RequiredArgsConstructor(access = AccessLevel.PRIVATE) public class SimpleTbCacheValueWrapper implements TbCacheValueWrapper { diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java index 664eaa1fb4..9cb139e26a 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java @@ -67,7 +67,7 @@ public class TbCaffeineCacheConfiguration { //SimpleCacheManager is not a bean (will be wrapped), so call initializeCaches manually manager.initializeCaches(); - return new TransactionAwareCacheManagerProxy(manager); + return manager; } private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) { diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java new file mode 100644 index 0000000000..ff1616d532 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbFSTRedisSerializer.java @@ -0,0 +1,32 @@ +/** + * 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.cache; + +import org.springframework.data.redis.serializer.SerializationException; +import org.thingsboard.server.common.data.FSTUtils; + +public class TbFSTRedisSerializer implements TbRedisSerializer { + + @Override + public byte[] serialize(V value) throws SerializationException { + return FSTUtils.encode(value); + } + + @Override + public V deserialize(K key, byte[] bytes) throws SerializationException { + return FSTUtils.decode(bytes); + } +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSerializer.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSerializer.java index 574038ea91..cb8a3cfa6a 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSerializer.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSerializer.java @@ -15,20 +15,15 @@ */ package org.thingsboard.server.cache; -import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; +import org.springframework.lang.Nullable; -public class TbRedisSerializer implements RedisSerializer { +public interface TbRedisSerializer { - private final RedisSerializer java = RedisSerializer.java(); + @Nullable + byte[] serialize(@Nullable T t) throws SerializationException; - @Override - public byte[] serialize(T t) throws SerializationException { - return java.serialize(t); - } + @Nullable + T deserialize(K key, @Nullable byte[] bytes) throws SerializationException; - @Override - public T deserialize(byte[] bytes) throws SerializationException { - return (T) java.deserialize(bytes); - } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java index 92ab0489c7..302b0e8187 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbTransactionalCache.java @@ -39,6 +39,12 @@ public interface TbTransactionalCache newTransactionForKey(K key); + /** + * Note that all keys should be in the same cache slot for redis. You may control the cache slot using '{}' bracers. + * See CLUSTER KEYSLOT command for more details. + * @param keys - list of keys to use + * @return transaction object + */ TbCacheTransaction newTransactionForKeys(List keys); default V getAndPutInTransaction(K key, Supplier dbCall, boolean cacheNullValue) { diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java index 99577f0039..805e70263a 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/device/DeviceRedisCache.java @@ -21,7 +21,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.cache.TbFSTRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.Device; @@ -30,6 +30,6 @@ import org.thingsboard.server.common.data.Device; public class DeviceRedisCache extends RedisTbTransactionalCache { public DeviceRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.DEVICE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java b/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java index 7e65d6a230..f561119f7f 100644 --- a/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java +++ b/common/cache/src/test/java/org/thingsboard/server/cache/CacheSpecsMapTest.java @@ -21,8 +21,8 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cache.CacheManager; -import org.springframework.cache.transaction.TransactionAwareCacheDecorator; -import org.springframework.cache.transaction.TransactionAwareCacheManagerProxy; +import org.springframework.cache.caffeine.CaffeineCache; +import org.springframework.cache.support.SimpleCacheManager; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit.jupiter.SpringExtension; @@ -45,14 +45,16 @@ public class CacheSpecsMapTest { CacheManager cacheManager; @Test - public void verifyTransactionAwareCacheManagerProxy() { - assertThat(cacheManager).isInstanceOf(TransactionAwareCacheManagerProxy.class); + public void verifyNotTransactionAwareCacheManagerProxy() { + // We no longer use built-in transaction support for the caches, because we have our own cache cleanup and transaction logic that implements CAS. + assertThat(cacheManager).isInstanceOf(SimpleCacheManager.class); } @Test - public void givenCacheConfig_whenCacheManagerReady_thenVerifyExistedCachesWithTransactionAwareCacheDecorator() { - assertThat(cacheManager.getCache("relations")).isInstanceOf(TransactionAwareCacheDecorator.class); - assertThat(cacheManager.getCache("devices")).isInstanceOf(TransactionAwareCacheDecorator.class); + public void givenCacheConfig_whenCacheManagerReady_thenVerifyExistedCachesWithNoTransactionAwareCacheDecorator() { + // We no longer use built-in transaction support for the caches, because we have our own cache cleanup and transaction logic that implements CAS. + assertThat(cacheManager.getCache("relations")).isInstanceOf(CaffeineCache.class); + assertThat(cacheManager.getCache("devices")).isInstanceOf(CaffeineCache.class); } @Test diff --git a/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java index 22953f259a..d258aa3820 100644 --- a/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java +++ b/common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java @@ -31,8 +31,9 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueClusterService; @@ -47,9 +48,11 @@ public interface TbClusterService extends TbQueueClusterService { void pushMsgToCore(ToDeviceActorNotificationMsg msg, TbQueueCallback callback); + void pushMsgToVersionControl(TenantId tenantId, ToVersionControlServiceMsg msg, TbQueueCallback callback); + void pushNotificationToCore(String targetServiceId, FromDeviceRpcResponse response, TbQueueCallback callback); - void pushMsgToRuleEngine(TopicPartitionInfo tpi, UUID msgId, TransportProtos.ToRuleEngineMsg msg, TbQueueCallback callback); + void pushMsgToRuleEngine(TopicPartitionInfo tpi, UUID msgId, ToRuleEngineMsg msg, TbQueueCallback callback); void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg msg, TbQueueCallback callback); @@ -85,5 +88,5 @@ public interface TbClusterService extends TbQueueClusterService { void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId); - void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action); + void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action); } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 50f114856f..1714e109a3 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -85,6 +85,17 @@ message KeyValueProto { string json_v = 7; } +message AttributeValueProto { + int64 lastUpdateTs = 1; + KeyValueType type = 2; + bool has_v = 3; + bool bool_v = 4; + int64 long_v = 5; + double double_v = 6; + string string_v = 7; + string json_v = 8; +} + message TsKvProto { int64 ts = 1; KeyValueProto kv = 2; @@ -262,7 +273,6 @@ message GetTenantRoutingInfoRequestMsg { } message GetTenantRoutingInfoResponseMsg { - bool isolatedTbCore = 1; bool isolatedTbRuleEngine = 2; } @@ -454,6 +464,14 @@ message GetOtaPackageResponseMsg { string fileName = 8; } +message DeviceActivityProto { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 deviceIdMSB = 3; + int64 deviceIdLSB = 4; + int64 lastActivityTime = 5; +} + //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. message SubscriptionInfoProto { int64 lastActivityTime = 1; @@ -618,8 +636,8 @@ message TbSubscriptionUpdateValueListProto { } message TbSubscriptionUpdateTsValue { - int64 ts = 1; - optional string value = 2; + int64 ts = 1; + optional string value = 2; } /** @@ -676,6 +694,188 @@ message EdgeNotificationMsgProto { PostAttributeMsg postAttributesMsg = 12; } +/** + TB Core to Version Control Service + */ +message CommitRequestMsg { + string txId = 1; // To correlate prepare, add, delete and push messages + PrepareMsg prepareMsg = 2; + AddMsg addMsg = 3; + DeleteMsg deleteMsg = 4; + PushMsg pushMsg = 5; + AbortMsg abortMsg = 6; +} + +message CommitResponseMsg { + int64 ts = 1; + string commitId = 2; + string name = 3; + string author = 4; + int32 added = 5; + int32 modified = 6; + int32 removed = 7; +} + +message PrepareMsg { + string commitMsg = 1; + string branchName = 2; + string authorName = 3; + string authorEmail = 4; +} + +message AddMsg { + string relativePath = 1; + string entityDataJsonChunk = 2; + string chunkedMsgId = 3; + int32 chunkIndex = 4; + int32 chunksCount = 5; +} + +message DeleteMsg { + string relativePath = 1; +} + +message PushMsg { +} + +message AbortMsg { +} + +message ListVersionsRequestMsg { + string branchName = 1; + string entityType = 2; + int64 entityIdMSB = 3; + int64 entityIdLSB = 4; + int32 pageSize = 5; + int32 page = 6; + string textSearch = 7; + string sortProperty = 8; + string sortDirection = 9; +} + +message EntityVersionProto { + int64 ts = 1; + string id = 2; + string name = 3; + string author = 4; +} + +message ListVersionsResponseMsg { + repeated EntityVersionProto versions = 1; + int32 totalPages = 2; + int64 totalElements = 3; + bool hasNext = 4; +} + +message ListEntitiesRequestMsg { + string versionId = 1; + string entityType = 2; +} + +message VersionedEntityInfoProto { + string entityType = 1; + int64 entityIdMSB = 2; + int64 entityIdLSB = 3; +} + +message ListEntitiesResponseMsg { + repeated VersionedEntityInfoProto entities = 1; +} + +message ListBranchesRequestMsg { +} + +message BranchInfoProto { + string name = 1; + bool isDefault = 2; +} + +message ListBranchesResponseMsg { + repeated BranchInfoProto branches = 1; +} + +message EntityContentRequestMsg { + string versionId = 1; + string entityType = 2; + int64 entityIdMSB = 3; + int64 entityIdLSB = 4; +} + +message EntityContentResponseMsg { + string data = 1; + int32 chunkIndex = 2; + int32 chunksCount = 3; +} + +message EntitiesContentRequestMsg { + string versionId = 1; + string entityType = 2; + int32 offset = 3; + int32 limit = 4; +} + +message EntitiesContentResponseMsg { + EntityContentResponseMsg item = 1; + int32 itemIdx = 2; + int32 itemsCount = 3; +} + +message VersionsDiffRequestMsg { + string path = 1; + string versionId1 = 2; + string versionId2 = 3; +} + +message VersionsDiffResponseMsg { + repeated EntityVersionsDiff diff = 1; +} + +message EntityVersionsDiff { + string entityType = 1; + int64 entityIdMSB = 2; + int64 entityIdLSB = 3; + string entityDataAtVersion1 = 4; + string entityDataAtVersion2 = 5; + string rawDiff = 6; +} + +message GenericRepositoryRequestMsg {} + +message GenericRepositoryResponseMsg {} + +message ToVersionControlServiceMsg { + string nodeId = 1; + int64 tenantIdMSB = 2; + int64 tenantIdLSB = 3; + int64 requestIdMSB = 4; + int64 requestIdLSB = 5; + bytes vcSettings = 6; + GenericRepositoryRequestMsg initRepositoryRequest = 7; + GenericRepositoryRequestMsg testRepositoryRequest = 8; + GenericRepositoryRequestMsg clearRepositoryRequest = 9; + CommitRequestMsg commitRequest = 10; + ListVersionsRequestMsg listVersionRequest = 11; + ListEntitiesRequestMsg listEntitiesRequest = 12; + ListBranchesRequestMsg listBranchesRequest = 13; + EntityContentRequestMsg entityContentRequest = 14; + EntitiesContentRequestMsg entitiesContentRequest = 15; + VersionsDiffRequestMsg versionsDiffRequest = 16; +} + +message VersionControlResponseMsg { + int64 requestIdMSB = 1; + int64 requestIdLSB = 2; + string error = 3; + GenericRepositoryResponseMsg genericResponse = 4; + CommitResponseMsg commitResponse = 5; + ListBranchesResponseMsg listBranchesResponse = 6; + ListEntitiesResponseMsg listEntitiesResponse = 7; + ListVersionsResponseMsg listVersionsResponse = 8; + EntityContentResponseMsg entityContentResponse = 9; + EntitiesContentResponseMsg entitiesContentResponse = 10; + VersionsDiffResponseMsg versionsDiffResponse = 11; +} + /** * Main messages; */ @@ -720,6 +920,7 @@ message ToCoreMsg { SubscriptionMgrMsgProto toSubscriptionMgrMsg = 3; bytes toDeviceActorNotificationMsg = 4; EdgeNotificationMsgProto edgeNotificationMsg = 5; + DeviceActivityProto deviceActivityMsg = 6; } /* High priority messages with low latency are handled by ThingsBoard Core Service separately */ @@ -730,6 +931,7 @@ message ToCoreNotificationMsg { bytes edgeEventUpdateMsg = 4; QueueUpdateMsg queueUpdateMsg = 5; QueueDeleteMsg queueDeleteMsg = 6; + VersionControlResponseMsg vcResponseMsg = 7; } /* Messages that are handled by ThingsBoard RuleEngine Service */ @@ -793,3 +995,5 @@ message ToOtaPackageStateServiceMsg { int64 otaPackageIdLSB = 7; string type = 8; } + + diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index 84c3f9332f..4032797b5b 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -25,6 +25,8 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import java.util.List; + public interface DashboardService { Dashboard findDashboardById(TenantId tenantId, DashboardId dashboardId); @@ -64,4 +66,7 @@ public interface DashboardService { PageData findDashboardsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink); DashboardInfo findFirstDashboardInfoByTenantIdAndName(TenantId tenantId, String name); + + List findTenantDashboardsByTitle(TenantId tenantId, String title); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java index 72ec2eeb7f..6029e742d7 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/ClaimDevicesService.java @@ -23,13 +23,11 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.device.claim.ReclaimResult; -import java.util.concurrent.ExecutionException; - public interface ClaimDevicesService { ListenableFuture registerClaimingInfo(TenantId tenantId, DeviceId deviceId, String secretKey, long durationMs); - ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey) throws ExecutionException, InterruptedException; + ListenableFuture claimDevice(Device device, CustomerId customerId, String secretKey); ListenableFuture reClaimDevice(TenantId tenantId, Device device); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index ea95923275..c0c72552f6 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -84,5 +84,5 @@ public interface EdgeService { PageData findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink); - String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId); + String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId, String tbRuleChainInputNodeClassName); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java index bbc011845a..bb790c78cb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.TimePageLink; import java.util.List; @@ -72,6 +71,8 @@ public interface EntityViewService { ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId); + List findEntityViewsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId); + void deleteEntityView(TenantId tenantId, EntityViewId entityViewId); void deleteEntityViewsByTenantId(TenantId tenantId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java index dbd641c1c8..e7df5eea93 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChainType; +import java.util.Collection; import java.util.List; /** @@ -34,12 +35,16 @@ import java.util.List; */ public interface RelationService { - ListenableFuture checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); + ListenableFuture checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); + + boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); EntityRelation getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); boolean saveRelation(TenantId tenantId, EntityRelation relation); + void saveRelations(TenantId tenantId, List relations); + ListenableFuture saveRelationAsync(TenantId tenantId, EntityRelation relation); boolean deleteRelation(TenantId tenantId, EntityRelation relation); @@ -52,8 +57,6 @@ public interface RelationService { void deleteEntityRelations(TenantId tenantId, EntityId entity); - ListenableFuture deleteEntityRelationsAsync(TenantId tenantId, EntityId entity); - List findByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); ListenableFuture> findByFromAsync(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 3328372f2e..9a856ae66a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -28,11 +28,11 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainData; import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainUpdateResult; import org.thingsboard.server.common.data.rule.RuleNode; +import java.util.Collection; import java.util.List; /** @@ -66,6 +66,8 @@ public interface RuleChainService { PageData findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, PageLink pageLink); + Collection findTenantRuleChainsByTypeAndName(TenantId tenantId, RuleChainType type, String name); + void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId); void deleteRuleChainsByTenantId(TenantId tenantId); @@ -97,4 +99,7 @@ public interface RuleChainService { PageData findAllRuleNodesByType(String type, PageLink pageLink); RuleNode saveRuleNode(TenantId tenantId, RuleNode ruleNode); + + void deleteRuleNodes(TenantId tenantId, RuleChainId ruleChainId); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java index d9238c9d7b..82839da1b8 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java @@ -25,6 +25,12 @@ public interface AdminSettingsService { AdminSettings findAdminSettingsByKey(TenantId tenantId, String key); + AdminSettings findAdminSettingsByTenantIdAndKey(TenantId tenantId, String key); + AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings); + boolean deleteAdminSettingsByTenantIdAndKey(TenantId tenantId, String key); + + void deleteAdminSettingsByTenantId(TenantId tenantId); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java index fbb9dfa0e1..2dcccc843f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TenantService.java @@ -35,6 +35,8 @@ public interface TenantService { Tenant saveTenant(Tenant tenant); + boolean tenantExists(TenantId tenantId); + void deleteTenant(TenantId tenantId); PageData findTenants(PageLink pageLink); @@ -44,4 +46,6 @@ public interface TenantService { List findTenantIdsByTenantProfileId(TenantProfileId tenantProfileId); void deleteTenants(); + + PageData findTenantsIds(PageLink pageLink); } diff --git a/common/data/pom.xml b/common/data/pom.xml index 1f2a6b10b1..31c17a92d9 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -104,6 +104,10 @@ io.swagger swagger-annotations + + de.ruedigermoeller + fst + com.google.protobuf protobuf-java-util diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java index 3b639659c8..ab92f267f6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java @@ -21,14 +21,17 @@ import org.thingsboard.server.common.data.id.AdminSettingsId; import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @ApiModel -public class AdminSettings extends BaseData { +public class AdminSettings extends BaseData implements HasTenantId { private static final long serialVersionUID = -7670322981725511892L; + private TenantId tenantId; + @NoXss @Length(fieldName = "key") private String key; @@ -44,6 +47,7 @@ public class AdminSettings extends BaseData { public AdminSettings(AdminSettings adminSettings) { super(adminSettings); + this.tenantId = adminSettings.getTenantId(); this.key = adminSettings.getKey(); this.jsonValue = adminSettings.getJsonValue(); } @@ -54,13 +58,22 @@ public class AdminSettings extends BaseData { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the settings creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the settings creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "The Administration Settings key, (e.g. 'general' or 'mail')", example = "mail") + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + @ApiModelProperty(position = 4, value = "The Administration Settings key, (e.g. 'general' or 'mail')", example = "mail") public String getKey() { return key; } @@ -69,7 +82,7 @@ public class AdminSettings extends BaseData { this.key = key; } - @ApiModelProperty(position = 4, value = "JSON representation of the Administration Settings value") + @ApiModelProperty(position = 5, value = "JSON representation of the Administration Settings value") public JsonNode getJsonValue() { return jsonValue; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java index c8cf730ead..f2ada352e7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java @@ -65,9 +65,7 @@ public abstract class BaseData extends IdBased implement if (getClass() != obj.getClass()) return false; BaseData other = (BaseData) obj; - if (createdTime != other.createdTime) - return false; - return true; + return createdTime == other.createdTime; } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index 49db7befd3..3d5578adf8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -26,10 +26,15 @@ public class CacheConstants { public static final String CLAIM_DEVICES_CACHE = "claimDevices"; public static final String SECURITY_SETTINGS_CACHE = "securitySettings"; public static final String TENANT_PROFILE_CACHE = "tenantProfiles"; + public static final String TENANTS_CACHE = "tenants"; + public static final String TENANTS_EXIST_CACHE = "tenantsExist"; public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String ATTRIBUTES_CACHE = "attributes"; - public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime"; + public static final String USERS_UPDATE_TIME_CACHE = "usersUpdateTime"; public static final String OTA_PACKAGE_CACHE = "otaPackages"; public static final String OTA_PACKAGE_DATA_CACHE = "otaPackagesData"; + public static final String REPOSITORY_SETTINGS_CACHE = "repositorySettings"; + public static final String AUTO_COMMIT_SETTINGS_CACHE = "autoCommitSettings"; public static final String TWO_FA_VERIFICATION_CODES_CACHE = "twoFaVerificationCodes"; + public static final String VERSION_CONTROL_TASK_CACHE = "versionControlTask"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java index ab21d70795..bdd06606a6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Customer.java @@ -20,12 +20,16 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty.Access; import com.fasterxml.jackson.databind.JsonNode; import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; -public class Customer extends ContactBased implements HasTenantId { +@EqualsAndHashCode(callSuper = true) +public class Customer extends ContactBased implements HasTenantId, ExportableEntity { private static final long serialVersionUID = -1599722990298929275L; @@ -36,6 +40,9 @@ public class Customer extends ContactBased implements HasTenantId { @ApiModelProperty(position = 5, required = true, value = "JSON object with Tenant Id") private TenantId tenantId; + @Getter @Setter + private CustomerId externalId; + public Customer() { super(); } @@ -48,6 +55,7 @@ public class Customer extends ContactBased implements HasTenantId { super(customer); this.tenantId = customer.getTenantId(); this.title = customer.getTitle(); + this.externalId = customer.getExternalId(); } public TenantId getTenantId() { @@ -75,7 +83,7 @@ public class Customer extends ContactBased implements HasTenantId { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the customer creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the customer creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); @@ -151,7 +159,7 @@ public class Customer extends ContactBased implements HasTenantId { @Override @JsonProperty(access = Access.READ_ONLY) - @ApiModelProperty(position = 4, value = "Name of the customer. Read-only, duplicated from title for backward compatibility", example = "Company A", readOnly = true) + @ApiModelProperty(position = 4, value = "Name of the customer. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public String getName() { return title; } @@ -161,37 +169,6 @@ public class Customer extends ContactBased implements HasTenantId { return getTitle(); } - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((tenantId == null) ? 0 : tenantId.hashCode()); - result = prime * result + ((title == null) ? 0 : title.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - Customer other = (Customer) obj; - if (tenantId == null) { - if (other.tenantId != null) - return false; - } else if (!tenantId.equals(other.tenantId)) - return false; - if (title == null) { - if (other.title != null) - return false; - } else if (!title.equals(other.title)) - return false; - return true; - } - @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java index 450c88cbb5..f0d0d88f68 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java @@ -15,16 +15,32 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Streams; import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.id.DashboardId; -public class Dashboard extends DashboardInfo { +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@EqualsAndHashCode(callSuper = true) +public class Dashboard extends DashboardInfo implements ExportableEntity { private static final long serialVersionUID = 872682138346187503L; - + private transient JsonNode configuration; - + + @Getter + @Setter + private DashboardId externalId; + public Dashboard() { super(); } @@ -40,6 +56,7 @@ public class Dashboard extends DashboardInfo { public Dashboard(Dashboard dashboard) { super(dashboard); this.configuration = dashboard.getConfiguration(); + this.externalId = dashboard.getExternalId(); } @ApiModelProperty(position = 9, value = "JSON object with main configuration of the dashboard: layouts, widgets, aliases, etc. " + @@ -54,29 +71,26 @@ public class Dashboard extends DashboardInfo { this.configuration = configuration; } - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((configuration == null) ? 0 : configuration.hashCode()); - return result; + @JsonIgnore + public List getEntityAliasesConfig() { + return getChildObjects("entityAliases"); } - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - Dashboard other = (Dashboard) obj; - if (configuration == null) { - if (other.configuration != null) - return false; - } else if (!configuration.equals(other.configuration)) - return false; - return true; + @JsonIgnore + public List getWidgetsConfig() { + return getChildObjects("widgets"); + } + + @JsonIgnore + private List getChildObjects(String propertyName) { + return Optional.ofNullable(configuration) + .map(config -> config.get(propertyName)) + .filter(node -> !node.isEmpty() && (node.isObject() || node.isArray())) + .map(node -> Streams.stream(node.elements()) + .filter(JsonNode::isObject) + .map(jsonNode -> (ObjectNode) jsonNode) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index e603f68c77..1895b41639 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.validation.NoXss; import javax.validation.Valid; import java.util.HashSet; +import java.util.Objects; import java.util.Set; @ApiModel @@ -63,19 +65,19 @@ public class DashboardInfo extends SearchTextBased implements HasNa @ApiModelProperty(position = 1, value = "JSON object with the dashboard Id. " + "Specify existing dashboard Id to update the dashboard. " + "Referencing non-existing dashboard id will cause error. " + - "Omit this field to create new dashboard." ) + "Omit this field to create new dashboard.") @Override public DashboardId getId() { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the dashboard creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the dashboard creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the dashboard can't be changed.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the dashboard can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -93,7 +95,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa this.title = title; } - @ApiModelProperty(position = 8, value = "Thumbnail picture for rendering of the dashboards in a grid view on mobile devices.", readOnly = true) + @ApiModelProperty(position = 8, value = "Thumbnail picture for rendering of the dashboards in a grid view on mobile devices.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public String getImage() { return image; } @@ -102,7 +104,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa this.image = image; } - @ApiModelProperty(position = 5, value = "List of assigned customers with their info.", readOnly = true) + @ApiModelProperty(position = 5, value = "List of assigned customers with their info.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public Set getAssignedCustomers() { return assignedCustomers; } @@ -111,7 +113,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa this.assignedCustomers = assignedCustomers; } - @ApiModelProperty(position = 6, value = "Hide dashboard from mobile devices. Useful if the dashboard is not designed for small screens.", readOnly = true) + @ApiModelProperty(position = 6, value = "Hide dashboard from mobile devices. Useful if the dashboard is not designed for small screens.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public boolean isMobileHide() { return mobileHide; } @@ -120,7 +122,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa this.mobileHide = mobileHide; } - @ApiModelProperty(position = 7, value = "Order on mobile devices. Useful to adjust sorting of the dashboards for mobile applications", readOnly = true) + @ApiModelProperty(position = 7, value = "Order on mobile devices. Useful to adjust sorting of the dashboards for mobile applications", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public Integer getMobileOrder() { return mobileOrder; } @@ -133,7 +135,6 @@ public class DashboardInfo extends SearchTextBased implements HasNa return this.assignedCustomers != null && this.assignedCustomers.contains(new ShortCustomerInfo(customerId, null, false)); } - public ShortCustomerInfo getAssignedCustomerInfo(CustomerId customerId) { if (this.assignedCustomers != null) { for (ShortCustomerInfo customerInfo : this.assignedCustomers) { @@ -179,7 +180,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa } } - @ApiModelProperty(position = 4, value = "Same as title of the dashboard. Read-only field. Update the 'title' to change the 'name' of the dashboard.", readOnly = true) + @ApiModelProperty(position = 4, value = "Same as title of the dashboard. Read-only field. Update the 'title' to change the 'name' of the dashboard.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { @@ -201,25 +202,17 @@ public class DashboardInfo extends SearchTextBased implements HasNa } @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - DashboardInfo other = (DashboardInfo) obj; - if (tenantId == null) { - if (other.tenantId != null) - return false; - } else if (!tenantId.equals(other.tenantId)) - return false; - if (title == null) { - if (other.title != null) - return false; - } else if (!title.equals(other.title)) - return false; - return true; + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + DashboardInfo that = (DashboardInfo) o; + return mobileHide == that.mobileHide + && Objects.equals(tenantId, that.tenantId) + && Objects.equals(title, that.title) + && Objects.equals(image, that.image) + && Objects.equals(assignedCustomers, that.assignedCustomers) + && Objects.equals(mobileOrder, that.mobileOrder); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index 4d7bf5e822..4a6589d72c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -21,6 +21,8 @@ import com.fasterxml.jackson.databind.JsonNode; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; @@ -38,7 +40,7 @@ import java.util.Optional; @ApiModel @EqualsAndHashCode(callSuper = true) @Slf4j -public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, HasOtaPackage { +public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, HasOtaPackage, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; @@ -61,6 +63,9 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen private OtaPackageId firmwareId; private OtaPackageId softwareId; + @Getter @Setter + private DeviceId externalId; + public Device() { super(); } @@ -80,6 +85,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.setDeviceData(device.getDeviceData()); this.firmwareId = device.getFirmwareId(); this.softwareId = device.getSoftwareId(); + this.externalId = device.getExternalId(); } public Device updateDevice(Device device) { @@ -93,6 +99,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.setFirmwareId(device.getFirmwareId()); this.setSoftwareId(device.getSoftwareId()); Optional.ofNullable(device.getAdditionalInfo()).ifPresent(this::setAdditionalInfo); + this.setExternalId(device.getExternalId()); return this; } @@ -105,13 +112,13 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the device creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the device creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -120,7 +127,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public CustomerId getCustomerId() { return customerId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java index a2e569d602..ccb9c29b6b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java @@ -24,11 +24,11 @@ import org.thingsboard.server.common.data.id.DeviceId; @Data public class DeviceInfo extends Device { - @ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", readOnly = true) + @ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String customerTitle; - @ApiModelProperty(position = 14, value = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", readOnly = true) + @ApiModelProperty(position = 14, value = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private boolean customerIsPublic; - @ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", readOnly = true) + @ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String deviceProfileName; public DeviceInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index f432862499..da8edc695e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -45,11 +45,11 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn @ToString(exclude = {"image", "profileDataBytes"}) @EqualsAndHashCode(callSuper = true) @Slf4j -public class DeviceProfile extends SearchTextBased implements HasName, HasTenantId, HasOtaPackage { +public class DeviceProfile extends SearchTextBased implements HasName, HasTenantId, HasOtaPackage, ExportableEntity { private static final long serialVersionUID = 6998485460273302018L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id that owns the profile.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "name") @@ -74,12 +74,11 @@ public class DeviceProfile extends SearchTextBased implements H private RuleChainId defaultRuleChainId; @ApiModelProperty(position = 6, value = "Reference to the dashboard. Used in the mobile application to open the default dashboard when user navigates to device details.") private DashboardId defaultDashboardId; + @NoXss - @ApiModelProperty(position = 8, value = "Reference to the rule engine queue. " + + @ApiModelProperty(position = 8, value = "Rule engine queue name. " + "If present, the specified queue will be used to store all unprocessed messages related to device, including telemetry, attribute updates, etc. " + "Otherwise, the 'Main' queue will be used to store those messages.") - private QueueId defaultQueueId; - private String defaultQueueName; @Valid private transient DeviceProfileData profileData; @@ -94,6 +93,8 @@ public class DeviceProfile extends SearchTextBased implements H @ApiModelProperty(position = 10, value = "Reference to the software OTA package. If present, the specified package will be used as default device software. ") private OtaPackageId softwareId; + private DeviceProfileId externalId; + public DeviceProfile() { super(); } @@ -111,11 +112,12 @@ public class DeviceProfile extends SearchTextBased implements H this.isDefault = deviceProfile.isDefault(); this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); this.defaultDashboardId = deviceProfile.getDefaultDashboardId(); - this.defaultQueueId = deviceProfile.getDefaultQueueId(); + this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.setProfileData(deviceProfile.getProfileData()); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); this.firmwareId = deviceProfile.getFirmwareId(); this.softwareId = deviceProfile.getSoftwareId(); + this.externalId = deviceProfile.getExternalId(); } @ApiModelProperty(position = 1, value = "JSON object with the device profile Id. " + @@ -127,7 +129,7 @@ public class DeviceProfile extends SearchTextBased implements H return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); @@ -171,13 +173,4 @@ public class DeviceProfile extends SearchTextBased implements H } } - @JsonIgnore - public String getDefaultQueueName() { - return defaultQueueName; - } - - @JsonProperty - public void setDefaultQueueName(String defaultQueueName) { - this.defaultQueueName = defaultQueueName; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java index a05950d11d..f99d5181f7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java @@ -63,4 +63,9 @@ public class DeviceProfileInfo extends EntityInfo { this.transportType = transportType; } + public DeviceProfileInfo(DeviceProfile profile) { + this(profile.getId(), profile.getName(), profile.getImage(), profile.getDefaultDashboardId(), + profile.getType(), profile.getTransportType()); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java index d62efd6fcb..0155aecc96 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java @@ -60,6 +60,10 @@ public final class EdgeUtils { return EdgeEventType.WIDGETS_BUNDLE; case WIDGET_TYPE: return EdgeEventType.WIDGET_TYPE; + case OTA_PACKAGE: + return EdgeEventType.OTA_PACKAGE; + case QUEUE: + return EdgeEventType.QUEUE; default: log.warn("Unsupported entity type [{}]", entityType); return null; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java index 1f238fd672..51129801e3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java @@ -36,7 +36,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @AllArgsConstructor @EqualsAndHashCode(callSuper = true) public class EntityView extends SearchTextBasedWithAdditionalInfo - implements HasName, HasTenantId, HasCustomerId { + implements HasName, HasTenantId, HasCustomerId, ExportableEntity { private static final long serialVersionUID = 5582010124562018986L; @@ -59,6 +59,8 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo @ApiModelProperty(position = 10, value = "Represents the end time of the interval that is used to limit access to target device telemetry. Customer will not be able to see entity telemetry that is outside the specified interval;") private long endTimeMs; + private EntityViewId externalId; + public EntityView() { super(); } @@ -77,6 +79,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo this.keys = entityView.getKeys(); this.startTimeMs = entityView.getStartTimeMs(); this.endTimeMs = entityView.getEndTimeMs(); + this.externalId = entityView.getExternalId(); } @Override @@ -84,7 +87,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo return getName() /*What the ...*/; } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEntityViewToCustomer' to change the Customer Id.", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEntityViewToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public CustomerId getCustomerId() { return customerId; @@ -95,7 +98,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo return name; } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public TenantId getTenantId() { return tenantId; @@ -110,7 +113,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the Entity View creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the Entity View creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java index 4b8ac3f066..b9acde7af0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityViewInfo.java @@ -22,9 +22,9 @@ import org.thingsboard.server.common.data.id.EntityViewId; @Data public class EntityViewInfo extends EntityView { - @ApiModelProperty(position = 12, value = "Title of the Customer that owns the entity view.", readOnly = true) + @ApiModelProperty(position = 12, value = "Title of the Customer that owns the entity view.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String customerTitle; - @ApiModelProperty(position = 13, value = "Indicates special 'Public' Customer that is auto-generated to use the entity view on public dashboards.", readOnly = true) + @ApiModelProperty(position = 13, value = "Indicates special 'Public' Customer that is auto-generated to use the entity view on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private boolean customerIsPublic; public EntityViewInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Event.java b/common/data/src/main/java/org/thingsboard/server/common/data/Event.java index 6ff574917d..29ab33d075 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Event.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Event.java @@ -30,13 +30,13 @@ import org.thingsboard.server.common.data.id.TenantId; @ApiModel public class Event extends BaseData { - @ApiModelProperty(position = 1, value = "JSON object with Tenant Id.", readOnly = true) + @ApiModelProperty(position = 1, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; @ApiModelProperty(position = 2, value = "Event type", example = "STATS") private String type; @ApiModelProperty(position = 3, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") private String uid; - @ApiModelProperty(position = 4, value = "JSON object with Entity Id for which event is created.", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Entity Id for which event is created.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private EntityId entityId; @ApiModelProperty(position = 5, value = "Event body.", dataType = "com.fasterxml.jackson.databind.JsonNode") private transient JsonNode body; @@ -53,7 +53,7 @@ public class Event extends BaseData { super(event); } - @ApiModelProperty(position = 6, value = "Timestamp of the event creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 6, value = "Timestamp of the event creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java new file mode 100644 index 0000000000..57541d02e2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ExportableEntity.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import io.swagger.annotations.ApiModelProperty; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.HasId; +import org.thingsboard.server.common.data.id.TenantId; + +public interface ExportableEntity extends HasId, HasName { + + void setId(I id); + + @ApiModelProperty(position = 100, value = "JSON object with External Id from the VCS", accessMode = ApiModelProperty.AccessMode.READ_ONLY, hidden = true) + I getExternalId(); + + void setExternalId(I externalId); + + long getCreatedTime(); + + void setCreatedTime(long createdTime); + + void setTenantId(TenantId tenantId); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FSTUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/FSTUtils.java new file mode 100644 index 0000000000..f9da8f44fb --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FSTUtils.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.extern.slf4j.Slf4j; +import org.nustaq.serialization.FSTConfiguration; + +@Slf4j +public class FSTUtils { + + public static final FSTConfiguration CONFIG = FSTConfiguration.createDefaultConfiguration(); + + @SuppressWarnings("unchecked") + public static T decode(byte[] byteArray) { + return byteArray != null && byteArray.length > 0 ? (T) CONFIG.asObject(byteArray) : null; + } + + public static byte[] encode(T msq) { + return CONFIG.asByteArray(msq); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java index 3a17aefab8..b708979006 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java @@ -30,7 +30,7 @@ public class OtaPackage extends OtaPackageInfo { private static final long serialVersionUID = 3091601761339422546L; - @ApiModelProperty(position = 16, value = "OTA Package data.", readOnly = true) + @ApiModelProperty(position = 16, value = "OTA Package data.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private transient ByteBuffer data; public OtaPackage() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java index c860d06a4c..b31d0a6bc0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java @@ -40,44 +40,44 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo split(String value, int maxPartSize) { + return Splitter.fixedLength(maxPartSize).split(value); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java index da74df8be6..e634957134 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResource.java @@ -32,10 +32,10 @@ public class TbResource extends TbResourceInfo { @NoXss @Length(fieldName = "file name") - @ApiModelProperty(position = 8, value = "Resource file name.", example = "19.xml", readOnly = true) + @ApiModelProperty(position = 8, value = "Resource file name.", example = "19.xml", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String fileName; - @ApiModelProperty(position = 9, value = "Resource data.", example = "77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLQpGSUxFIElORk9STUFUSU9OCgpPTUEgUGVybWFuZW50IERvY3VtZW50CiAgIEZpbGU6IE9NQS1TVVAtTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci1WMV8wXzEtMjAxOTAyMjEtQQogICBUeXBlOiB4bWwKClB1YmxpYyBSZWFjaGFibGUgSW5mb3JtYXRpb24KICAgUGF0aDogaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3RlY2gvcHJvZmlsZXMKICAgTmFtZTogTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci12MV8wXzEueG1sCgpOT1JNQVRJVkUgSU5GT1JNQVRJT04KCiAgSW5mb3JtYXRpb24gYWJvdXQgdGhpcyBmaWxlIGNhbiBiZSBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJldmlzaW9uIG9mCgogIE9NQS1UUy1MV00yTV9CaW5hcnlBcHBEYXRhQ29udGFpbmVyLVYxXzBfMQoKICBUaGlzIGlzIGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvCgogIFNlbmQgY29tbWVudHMgdG8gaHR0cHM6Ly9naXRodWIuY29tL09wZW5Nb2JpbGVBbGxpYW5jZS9PTUFfTHdNMk1fZm9yX0RldmVsb3BlcnMvaXNzdWVzCgpDSEFOR0UgSElTVE9SWQoKMTUwNjIwMTggU3RhdHVzIGNoYW5nZWQgdG8gQXBwcm92ZWQgYnkgRE0sIERvYyBSZWYgIyBPTUEtRE0mU0UtMjAxOC0wMDYxLUlOUF9MV00yTV9BUFBEQVRBX1YxXzBfRVJQX2Zvcl9maW5hbF9BcHByb3ZhbAoyMTAyMjAxOSBTdGF0dXMgY2hhbmdlZCB0byBBcHByb3ZlZCBieSBJUFNPLCBEb2MgUmVmICMgT01BLUlQU08tMjAxOS0wMDI1LUlOUF9Md00yTV9PYmplY3RfQXBwX0RhdGFfQ29udGFpbmVyXzFfMF8xX2Zvcl9GaW5hbF9BcHByb3ZhbAoKTEVHQUwgRElTQ0xBSU1FUgoKQ29weXJpZ2h0IDIwMTkgT3BlbiBNb2JpbGUgQWxsaWFuY2UuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zCmFyZSBtZXQ6CgoxLiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodApub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCjIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0Cm5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUKZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KMy4gTmVpdGhlciB0aGUgbmFtZSBvZiB0aGUgY29weXJpZ2h0IGhvbGRlciBub3IgdGhlIG5hbWVzIG9mIGl0cwpjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQKZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUwpGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpDT1BZUklHSFQgSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULApJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLApCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIKQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUCkxJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOCkFOWSBXQVkgT1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRQpQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KClRoZSBhYm92ZSBsaWNlbnNlIGlzIHVzZWQgYXMgYSBsaWNlbnNlIHVuZGVyIGNvcHlyaWdodCBvbmx5LiBQbGVhc2UKcmVmZXJlbmNlIHRoZSBPTUEgSVBSIFBvbGljeSBmb3IgcGF0ZW50IGxpY2Vuc2luZyB0ZXJtczoKaHR0cHM6Ly93d3cub21hc3BlY3dvcmtzLm9yZy9hYm91dC9pbnRlbGxlY3R1YWwtcHJvcGVydHktcmlnaHRzLwoKLS0+CjxMV00yTSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6bm9OYW1lc3BhY2VTY2hlbWFMb2NhdGlvbj0iaHR0cDovL29wZW5tb2JpbGVhbGxpYW5jZS5vcmcvdGVjaC9wcm9maWxlcy9MV00yTS54c2QiPgoJPE9iamVjdCBPYmplY3RUeXBlPSJNT0RlZmluaXRpb24iPgoJCTxOYW1lPkJpbmFyeUFwcERhdGFDb250YWluZXI8L05hbWU+CgkJPERlc2NyaXB0aW9uMT48IVtDREFUQVtUaGlzIEx3TTJNIE9iamVjdHMgcHJvdmlkZXMgdGhlIGFwcGxpY2F0aW9uIHNlcnZpY2UgZGF0YSByZWxhdGVkIHRvIGEgTHdNMk0gU2VydmVyLCBlZy4gV2F0ZXIgbWV0ZXIgZGF0YS4gClRoZXJlIGFyZSBzZXZlcmFsIG1ldGhvZHMgdG8gY3JlYXRlIGluc3RhbmNlIHRvIGluZGljYXRlIHRoZSBtZXNzYWdlIGRpcmVjdGlvbiBiYXNlZCBvbiB0aGUgbmVnb3RpYXRpb24gYmV0d2VlbiBBcHBsaWNhdGlvbiBhbmQgTHdNMk0uIFRoZSBDbGllbnQgYW5kIFNlcnZlciBzaG91bGQgbmVnb3RpYXRlIHRoZSBpbnN0YW5jZShzKSB1c2VkIHRvIGV4Y2hhbmdlIHRoZSBkYXRhLiBGb3IgZXhhbXBsZToKIC0gVXNpbmcgYSBzaW5nbGUgaW5zdGFuY2UgZm9yIGJvdGggZGlyZWN0aW9ucyBjb21tdW5pY2F0aW9uLCBmcm9tIENsaWVudCB0byBTZXJ2ZXIgYW5kIGZyb20gU2VydmVyIHRvIENsaWVudC4KIC0gVXNpbmcgYW4gaW5zdGFuY2UgZm9yIGNvbW11bmljYXRpb24gZnJvbSBDbGllbnQgdG8gU2VydmVyIGFuZCBhbm90aGVyIG9uZSBmb3IgY29tbXVuaWNhdGlvbiBmcm9tIFNlcnZlciB0byBDbGllbnQKIC0gVXNpbmcgc2V2ZXJhbCBpbnN0YW5jZXMKXV0+PC9EZXNjcmlwdGlvbjE+CgkJPE9iamVjdElEPjE5PC9PYmplY3RJRD4KCQk8T2JqZWN0VVJOPnVybjpvbWE6bHdtMm06b21hOjE5PC9PYmplY3RVUk4+CgkJPExXTTJNVmVyc2lvbj4xLjA8L0xXTTJNVmVyc2lvbj4KCQk8T2JqZWN0VmVyc2lvbj4xLjA8L09iamVjdFZlcnNpb24+CgkJPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJPFJlc291cmNlcz4KCQkJPEl0ZW0gSUQ9IjAiPjxOYW1lPkRhdGE8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5NdWx0aXBsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+T3BhcXVlPC9UeXBlPgoJCQkJPFJhbmdlRW51bWVyYXRpb24gLz4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgYXBwbGljYXRpb24gZGF0YSBjb250ZW50Ll1dPjwvRGVzY3JpcHRpb24+CgkJCTwvSXRlbT4KCQkJPEl0ZW0gSUQ9IjEiPjxOYW1lPkRhdGEgUHJpb3JpdHk8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgoJCQkJPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+SW50ZWdlcjwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjEgYnl0ZXM8L1JhbmdlRW51bWVyYXRpb24+CgkJCQk8VW5pdHMgLz4KCQkJCTxEZXNjcmlwdGlvbj48IVtDREFUQVtJbmRpY2F0ZXMgdGhlIEFwcGxpY2F0aW9uIGRhdGEgcHJpb3JpdHk6CjA6SW1tZWRpYXRlCjE6QmVzdEVmZm9ydAoyOkxhdGVzdAozLTEwMDogUmVzZXJ2ZWQgZm9yIGZ1dHVyZSB1c2UuCjEwMS0yNTQ6IFByb3ByaWV0YXJ5IG1vZGUuXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iMiI+PE5hbWU+RGF0YSBDcmVhdGlvbiBUaW1lPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlRpbWU8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbiAvPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBEYXRhIGluc3RhbmNlIGNyZWF0aW9uIHRpbWVzdGFtcC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSIzIj48TmFtZT5EYXRhIERlc2NyaXB0aW9uPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlN0cmluZzwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjMyIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkYXRhIGRlc2NyaXB0aW9uLgplLmcuICJtZXRlciByZWFkaW5nIi5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSI0Ij48TmFtZT5EYXRhIEZvcm1hdDwvTmFtZT4KCQkJCTxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgoJCQkJPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJCQk8VHlwZT5TdHJpbmc8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4zMiBieXRlczwvUmFuZ2VFbnVtZXJhdGlvbj4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgZm9ybWF0IG9mIHRoZSBBcHBsaWNhdGlvbiBEYXRhLgplLmcuIFlHLU1ldGVyLVdhdGVyLVJlYWRpbmcKVVRGOC1zdHJpbmcKXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iNSI+PE5hbWU+QXBwIElEPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPkludGVnZXI8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4yIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkZXN0aW5hdGlvbiBBcHBsaWNhdGlvbiBJRC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+PC9SZXNvdXJjZXM+CgkJPERlc2NyaXB0aW9uMj48IVtDREFUQVtdXT48L0Rlc2NyaXB0aW9uMj4KCTwvT2JqZWN0Pgo8L0xXTTJNPgo=", readOnly = true) + @ApiModelProperty(position = 9, value = "Resource data.", example = "77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCEtLQpGSUxFIElORk9STUFUSU9OCgpPTUEgUGVybWFuZW50IERvY3VtZW50CiAgIEZpbGU6IE9NQS1TVVAtTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci1WMV8wXzEtMjAxOTAyMjEtQQogICBUeXBlOiB4bWwKClB1YmxpYyBSZWFjaGFibGUgSW5mb3JtYXRpb24KICAgUGF0aDogaHR0cDovL3d3dy5vcGVubW9iaWxlYWxsaWFuY2Uub3JnL3RlY2gvcHJvZmlsZXMKICAgTmFtZTogTHdNMk1fQmluYXJ5QXBwRGF0YUNvbnRhaW5lci12MV8wXzEueG1sCgpOT1JNQVRJVkUgSU5GT1JNQVRJT04KCiAgSW5mb3JtYXRpb24gYWJvdXQgdGhpcyBmaWxlIGNhbiBiZSBmb3VuZCBpbiB0aGUgbGF0ZXN0IHJldmlzaW9uIG9mCgogIE9NQS1UUy1MV00yTV9CaW5hcnlBcHBEYXRhQ29udGFpbmVyLVYxXzBfMQoKICBUaGlzIGlzIGF2YWlsYWJsZSBhdCBodHRwOi8vd3d3Lm9wZW5tb2JpbGVhbGxpYW5jZS5vcmcvCgogIFNlbmQgY29tbWVudHMgdG8gaHR0cHM6Ly9naXRodWIuY29tL09wZW5Nb2JpbGVBbGxpYW5jZS9PTUFfTHdNMk1fZm9yX0RldmVsb3BlcnMvaXNzdWVzCgpDSEFOR0UgSElTVE9SWQoKMTUwNjIwMTggU3RhdHVzIGNoYW5nZWQgdG8gQXBwcm92ZWQgYnkgRE0sIERvYyBSZWYgIyBPTUEtRE0mU0UtMjAxOC0wMDYxLUlOUF9MV00yTV9BUFBEQVRBX1YxXzBfRVJQX2Zvcl9maW5hbF9BcHByb3ZhbAoyMTAyMjAxOSBTdGF0dXMgY2hhbmdlZCB0byBBcHByb3ZlZCBieSBJUFNPLCBEb2MgUmVmICMgT01BLUlQU08tMjAxOS0wMDI1LUlOUF9Md00yTV9PYmplY3RfQXBwX0RhdGFfQ29udGFpbmVyXzFfMF8xX2Zvcl9GaW5hbF9BcHByb3ZhbAoKTEVHQUwgRElTQ0xBSU1FUgoKQ29weXJpZ2h0IDIwMTkgT3BlbiBNb2JpbGUgQWxsaWFuY2UuCgpSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXQKbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zCmFyZSBtZXQ6CgoxLiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodApub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuCjIuIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUgY29weXJpZ2h0Cm5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lciBpbiB0aGUKZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi4KMy4gTmVpdGhlciB0aGUgbmFtZSBvZiB0aGUgY29weXJpZ2h0IGhvbGRlciBub3IgdGhlIG5hbWVzIG9mIGl0cwpjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWQKZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgoKVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SUwoiQVMgSVMiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUwpGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRQpDT1BZUklHSFQgSE9MREVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULApJTkNJREVOVEFMLCBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLApCVVQgTk9UIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7CkxPU1MgT0YgVVNFLCBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIKQ0FVU0VEIEFORCBPTiBBTlkgVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUCkxJQUJJTElUWSwgT1IgVE9SVCAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOCkFOWSBXQVkgT1VUIE9GIFRIRSBVU0UgT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRQpQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS4KClRoZSBhYm92ZSBsaWNlbnNlIGlzIHVzZWQgYXMgYSBsaWNlbnNlIHVuZGVyIGNvcHlyaWdodCBvbmx5LiBQbGVhc2UKcmVmZXJlbmNlIHRoZSBPTUEgSVBSIFBvbGljeSBmb3IgcGF0ZW50IGxpY2Vuc2luZyB0ZXJtczoKaHR0cHM6Ly93d3cub21hc3BlY3dvcmtzLm9yZy9hYm91dC9pbnRlbGxlY3R1YWwtcHJvcGVydHktcmlnaHRzLwoKLS0+CjxMV00yTSB4bWxuczp4c2k9Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvWE1MU2NoZW1hLWluc3RhbmNlIiB4c2k6bm9OYW1lc3BhY2VTY2hlbWFMb2NhdGlvbj0iaHR0cDovL29wZW5tb2JpbGVhbGxpYW5jZS5vcmcvdGVjaC9wcm9maWxlcy9MV00yTS54c2QiPgoJPE9iamVjdCBPYmplY3RUeXBlPSJNT0RlZmluaXRpb24iPgoJCTxOYW1lPkJpbmFyeUFwcERhdGFDb250YWluZXI8L05hbWU+CgkJPERlc2NyaXB0aW9uMT48IVtDREFUQVtUaGlzIEx3TTJNIE9iamVjdHMgcHJvdmlkZXMgdGhlIGFwcGxpY2F0aW9uIHNlcnZpY2UgZGF0YSByZWxhdGVkIHRvIGEgTHdNMk0gU2VydmVyLCBlZy4gV2F0ZXIgbWV0ZXIgZGF0YS4gClRoZXJlIGFyZSBzZXZlcmFsIG1ldGhvZHMgdG8gY3JlYXRlIGluc3RhbmNlIHRvIGluZGljYXRlIHRoZSBtZXNzYWdlIGRpcmVjdGlvbiBiYXNlZCBvbiB0aGUgbmVnb3RpYXRpb24gYmV0d2VlbiBBcHBsaWNhdGlvbiBhbmQgTHdNMk0uIFRoZSBDbGllbnQgYW5kIFNlcnZlciBzaG91bGQgbmVnb3RpYXRlIHRoZSBpbnN0YW5jZShzKSB1c2VkIHRvIGV4Y2hhbmdlIHRoZSBkYXRhLiBGb3IgZXhhbXBsZToKIC0gVXNpbmcgYSBzaW5nbGUgaW5zdGFuY2UgZm9yIGJvdGggZGlyZWN0aW9ucyBjb21tdW5pY2F0aW9uLCBmcm9tIENsaWVudCB0byBTZXJ2ZXIgYW5kIGZyb20gU2VydmVyIHRvIENsaWVudC4KIC0gVXNpbmcgYW4gaW5zdGFuY2UgZm9yIGNvbW11bmljYXRpb24gZnJvbSBDbGllbnQgdG8gU2VydmVyIGFuZCBhbm90aGVyIG9uZSBmb3IgY29tbXVuaWNhdGlvbiBmcm9tIFNlcnZlciB0byBDbGllbnQKIC0gVXNpbmcgc2V2ZXJhbCBpbnN0YW5jZXMKXV0+PC9EZXNjcmlwdGlvbjE+CgkJPE9iamVjdElEPjE5PC9PYmplY3RJRD4KCQk8T2JqZWN0VVJOPnVybjpvbWE6bHdtMm06b21hOjE5PC9PYmplY3RVUk4+CgkJPExXTTJNVmVyc2lvbj4xLjA8L0xXTTJNVmVyc2lvbj4KCQk8T2JqZWN0VmVyc2lvbj4xLjA8L09iamVjdFZlcnNpb24+CgkJPE11bHRpcGxlSW5zdGFuY2VzPk11bHRpcGxlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJPFJlc291cmNlcz4KCQkJPEl0ZW0gSUQ9IjAiPjxOYW1lPkRhdGE8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5NdWx0aXBsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk1hbmRhdG9yeTwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+T3BhcXVlPC9UeXBlPgoJCQkJPFJhbmdlRW51bWVyYXRpb24gLz4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgYXBwbGljYXRpb24gZGF0YSBjb250ZW50Ll1dPjwvRGVzY3JpcHRpb24+CgkJCTwvSXRlbT4KCQkJPEl0ZW0gSUQ9IjEiPjxOYW1lPkRhdGEgUHJpb3JpdHk8L05hbWU+CgkJCQk8T3BlcmF0aW9ucz5SVzwvT3BlcmF0aW9ucz4KCQkJCTxNdWx0aXBsZUluc3RhbmNlcz5TaW5nbGU8L011bHRpcGxlSW5zdGFuY2VzPgoJCQkJPE1hbmRhdG9yeT5PcHRpb25hbDwvTWFuZGF0b3J5PgoJCQkJPFR5cGU+SW50ZWdlcjwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjEgYnl0ZXM8L1JhbmdlRW51bWVyYXRpb24+CgkJCQk8VW5pdHMgLz4KCQkJCTxEZXNjcmlwdGlvbj48IVtDREFUQVtJbmRpY2F0ZXMgdGhlIEFwcGxpY2F0aW9uIGRhdGEgcHJpb3JpdHk6CjA6SW1tZWRpYXRlCjE6QmVzdEVmZm9ydAoyOkxhdGVzdAozLTEwMDogUmVzZXJ2ZWQgZm9yIGZ1dHVyZSB1c2UuCjEwMS0yNTQ6IFByb3ByaWV0YXJ5IG1vZGUuXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iMiI+PE5hbWU+RGF0YSBDcmVhdGlvbiBUaW1lPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlRpbWU8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbiAvPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBEYXRhIGluc3RhbmNlIGNyZWF0aW9uIHRpbWVzdGFtcC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSIzIj48TmFtZT5EYXRhIERlc2NyaXB0aW9uPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPlN0cmluZzwvVHlwZT4KCQkJCTxSYW5nZUVudW1lcmF0aW9uPjMyIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkYXRhIGRlc2NyaXB0aW9uLgplLmcuICJtZXRlciByZWFkaW5nIi5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+CgkJCTxJdGVtIElEPSI0Ij48TmFtZT5EYXRhIEZvcm1hdDwvTmFtZT4KCQkJCTxPcGVyYXRpb25zPlJXPC9PcGVyYXRpb25zPgoJCQkJPE11bHRpcGxlSW5zdGFuY2VzPlNpbmdsZTwvTXVsdGlwbGVJbnN0YW5jZXM+CgkJCQk8TWFuZGF0b3J5Pk9wdGlvbmFsPC9NYW5kYXRvcnk+CgkJCQk8VHlwZT5TdHJpbmc8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4zMiBieXRlczwvUmFuZ2VFbnVtZXJhdGlvbj4KCQkJCTxVbml0cyAvPgoJCQkJPERlc2NyaXB0aW9uPjwhW0NEQVRBW0luZGljYXRlcyB0aGUgZm9ybWF0IG9mIHRoZSBBcHBsaWNhdGlvbiBEYXRhLgplLmcuIFlHLU1ldGVyLVdhdGVyLVJlYWRpbmcKVVRGOC1zdHJpbmcKXV0+PC9EZXNjcmlwdGlvbj4KCQkJPC9JdGVtPgoJCQk8SXRlbSBJRD0iNSI+PE5hbWU+QXBwIElEPC9OYW1lPgoJCQkJPE9wZXJhdGlvbnM+Ulc8L09wZXJhdGlvbnM+CgkJCQk8TXVsdGlwbGVJbnN0YW5jZXM+U2luZ2xlPC9NdWx0aXBsZUluc3RhbmNlcz4KCQkJCTxNYW5kYXRvcnk+T3B0aW9uYWw8L01hbmRhdG9yeT4KCQkJCTxUeXBlPkludGVnZXI8L1R5cGU+CgkJCQk8UmFuZ2VFbnVtZXJhdGlvbj4yIGJ5dGVzPC9SYW5nZUVudW1lcmF0aW9uPgoJCQkJPFVuaXRzIC8+CgkJCQk8RGVzY3JpcHRpb24+PCFbQ0RBVEFbSW5kaWNhdGVzIHRoZSBkZXN0aW5hdGlvbiBBcHBsaWNhdGlvbiBJRC5dXT48L0Rlc2NyaXB0aW9uPgoJCQk8L0l0ZW0+PC9SZXNvdXJjZXM+CgkJPERlc2NyaXB0aW9uMj48IVtDREFUQVtdXT48L0Rlc2NyaXB0aW9uMj4KCTwvT2JqZWN0Pgo8L0xXTTJNPgo=", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String data; public TbResource() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index 722918a4f3..e2b9a464e0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -34,19 +34,19 @@ public class TbResourceInfo extends SearchTextBased implements Has private static final long serialVersionUID = 7282664529021651736L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the resource can't be changed.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "title") @ApiModelProperty(position = 4, value = "Resource title.", example = "BinaryAppDataContainer id=19 v1.0") private String title; - @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", readOnly = true) + @ApiModelProperty(position = 5, value = "Resource type.", example = "LWM2M_MODEL", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private ResourceType resourceType; @NoXss @Length(fieldName = "resourceKey") - @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", readOnly = true) + @ApiModelProperty(position = 6, value = "Resource key.", example = "19_1.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String resourceKey; - @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", readOnly = true) + @ApiModelProperty(position = 7, value = "Resource search text.", example = "19_1.0:binaryappdatacontainer", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String searchText; public TbResourceInfo() { @@ -75,7 +75,7 @@ public class TbResourceInfo extends SearchTextBased implements Has return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the resource creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the resource creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java index 5a55669646..8d5a9fe3fc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Tenant.java @@ -74,7 +74,7 @@ public class Tenant extends ContactBased implements HasTenantId { } @Override - @ApiModelProperty(position = 4, value = "Name of the tenant. Read-only, duplicated from title for backward compatibility", example = "Company A", readOnly = true) + @ApiModelProperty(position = 4, value = "Name of the tenant. Read-only, duplicated from title for backward compatibility", example = "Company A", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { return title; @@ -110,7 +110,7 @@ public class Tenant extends ContactBased implements HasTenantId { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the tenant creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the tenant creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index e5cbf23179..89bd02344a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -53,13 +53,10 @@ public class TenantProfile extends SearchTextBased implements H private String description; @ApiModelProperty(position = 5, value = "Default Tenant profile to be used.", example = "true") private boolean isDefault; - @ApiModelProperty(position = 6, value = "If enabled, will push all messages related to this tenant and processed by core platform services into separate queue. " + - "Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "true") - private boolean isolatedTbCore; - @ApiModelProperty(position = 7, value = "If enabled, will push all messages related to this tenant and processed by the rule engine into separate queue. " + + @ApiModelProperty(position = 6, value = "If enabled, will push all messages related to this tenant and processed by the rule engine into separate queue. " + "Useful for complex microservices deployments, to isolate processing of the data for specific tenants", example = "true") private boolean isolatedTbRuleEngine; - @ApiModelProperty(position = 8, value = "Complex JSON object that contains profile settings: queue configs, max devices, max assets, rate limits, etc.") + @ApiModelProperty(position = 7, value = "Complex JSON object that contains profile settings: queue configs, max devices, max assets, rate limits, etc.") private transient TenantProfileData profileData; @JsonIgnore private byte[] profileDataBytes; @@ -77,7 +74,6 @@ public class TenantProfile extends SearchTextBased implements H this.name = tenantProfile.getName(); this.description = tenantProfile.getDescription(); this.isDefault = tenantProfile.isDefault(); - this.isolatedTbCore = tenantProfile.isIsolatedTbCore(); this.isolatedTbRuleEngine = tenantProfile.isIsolatedTbRuleEngine(); this.setProfileData(tenantProfile.getProfileData()); } @@ -85,13 +81,13 @@ public class TenantProfile extends SearchTextBased implements H @ApiModelProperty(position = 1, value = "JSON object with the tenant profile Id. " + "Specify this field to update the tenant profile. " + "Referencing non-existing tenant profile Id will cause error. " + - "Omit this field to create new tenant profile." ) + "Omit this field to create new tenant profile.") @Override public TenantProfileId getId() { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the tenant profile creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the tenant profile creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); @@ -132,9 +128,15 @@ public class TenantProfile extends SearchTextBased implements H .map(profileConfiguration -> (DefaultTenantProfileConfiguration) profileConfiguration); } + @JsonIgnore + public DefaultTenantProfileConfiguration getDefaultProfileConfiguration() { + return getProfileConfiguration().orElse(null); + } + public TenantProfileData createDefaultTenantProfileData() { TenantProfileData tpd = new TenantProfileData(); tpd.setConfiguration(new DefaultTenantProfileConfiguration()); + this.profileData = tpd; return tpd; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java index c207135f6d..60d8d7eca9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/User.java @@ -74,13 +74,13 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the user creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the user creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with the Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -89,7 +89,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with the Customer Id.", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public CustomerId getCustomerId() { return customerId; } @@ -107,7 +107,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H this.email = email; } - @ApiModelProperty(position = 6, readOnly = true, value = "Duplicates the email of the user, readonly", example = "user@example.com") + @ApiModelProperty(position = 6, accessMode = ApiModelProperty.AccessMode.READ_ONLY, value = "Duplicates the email of the user, readonly", example = "user@example.com") @Override @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 7587d687dd..f13c84bc82 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -43,10 +43,10 @@ import java.util.List; @AllArgsConstructor public class Alarm extends BaseData implements HasName, HasTenantId, HasCustomerId { - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 4, value = "JSON object with Customer Id", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private CustomerId customerId; @ApiModelProperty(position = 6, required = true, value = "representing type of the Alarm", example = "High Temperature Alarm") @@ -124,7 +124,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId, Ha } - @ApiModelProperty(position = 2, value = "Timestamp of the alarm creation, in milliseconds", example = "1634058704567", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the alarm creation, in milliseconds", example = "1634058704567", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java index db68df4901..f4ad335a43 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java @@ -19,6 +19,9 @@ import com.fasterxml.jackson.databind.JsonNode; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; @@ -33,7 +36,7 @@ import java.util.Optional; @ApiModel @EqualsAndHashCode(callSuper = true) -public class Asset extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId { +public class Asset extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, ExportableEntity { private static final long serialVersionUID = 2807343040519543363L; @@ -49,6 +52,9 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements @Length(fieldName = "label") private String label; + @Getter @Setter + private AssetId externalId; + public Asset() { super(); } @@ -64,6 +70,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements this.name = asset.getName(); this.type = asset.getType(); this.label = asset.getLabel(); + this.externalId = asset.getExternalId(); } public void update(Asset asset) { @@ -73,6 +80,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements this.type = asset.getType(); this.label = asset.getLabel(); Optional.ofNullable(asset.getAdditionalInfo()).ifPresent(this::setAdditionalInfo); + this.externalId = asset.getExternalId(); } @ApiModelProperty(position = 1, value = "JSON object with the asset Id. " + @@ -84,13 +92,13 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the asset creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the asset creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public TenantId getTenantId() { return tenantId; } @@ -99,7 +107,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements this.tenantId = tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public CustomerId getCustomerId() { return customerId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java index 4b962aa811..a6a532cbe6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java @@ -24,9 +24,9 @@ import org.thingsboard.server.common.data.id.AssetId; @Data public class AssetInfo extends Asset { - @ApiModelProperty(position = 9, value = "Title of the Customer that owns the asset.", readOnly = true) + @ApiModelProperty(position = 9, value = "Title of the Customer that owns the asset.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String customerTitle; - @ApiModelProperty(position = 10, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", readOnly = true) + @ApiModelProperty(position = 10, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private boolean customerIsPublic; public AssetInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java index 01f2336197..e87a1a6380 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/AuditLog.java @@ -28,25 +28,25 @@ import org.thingsboard.server.common.data.id.*; @Data public class AuditLog extends BaseData { - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 4, value = "JSON object with Customer Id", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Customer Id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private CustomerId customerId; - @ApiModelProperty(position = 5, value = "JSON object with Entity id", readOnly = true) + @ApiModelProperty(position = 5, value = "JSON object with Entity id", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private EntityId entityId; - @ApiModelProperty(position = 6, value = "Name of the logged entity", example = "Thermometer", readOnly = true) + @ApiModelProperty(position = 6, value = "Name of the logged entity", example = "Thermometer", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String entityName; - @ApiModelProperty(position = 7, value = "JSON object with User id.", readOnly = true) + @ApiModelProperty(position = 7, value = "JSON object with User id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private UserId userId; - @ApiModelProperty(position = 8, value = "Unique user name(email) of the user that performed some action on logged entity", example = "tenant@thingsboard.org", readOnly = true) + @ApiModelProperty(position = 8, value = "Unique user name(email) of the user that performed some action on logged entity", example = "tenant@thingsboard.org", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String userName; - @ApiModelProperty(position = 9, value = "String represented Action type", example = "ADDED", readOnly = true) + @ApiModelProperty(position = 9, value = "String represented Action type", example = "ADDED", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private ActionType actionType; - @ApiModelProperty(position = 10, value = "JsonNode represented action data", readOnly = true) + @ApiModelProperty(position = 10, value = "JsonNode represented action data", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private JsonNode actionData; - @ApiModelProperty(position = 11, value = "String represented Action status", example = "SUCCESS", allowableValues = "SUCCESS,FAILURE", readOnly = true) + @ApiModelProperty(position = 11, value = "String represented Action status", example = "SUCCESS", allowableValues = "SUCCESS,FAILURE", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private ActionStatus actionStatus; - @ApiModelProperty(position = 12, value = "Failure action details info. An empty string in case of action status type 'SUCCESS', otherwise includes stack trace of the caused exception.", readOnly = true) + @ApiModelProperty(position = 12, value = "Failure action details info. An empty string in case of action status type 'SUCCESS', otherwise includes stack trace of the caused exception.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String actionFailureDetails; public AuditLog() { @@ -71,7 +71,7 @@ public class AuditLog extends BaseData { this.actionFailureDetails = auditLog.getActionFailureDetails(); } - @ApiModelProperty(position = 2, value = "Timestamp of the auditLog creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the auditLog creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java index 8232f6670f..2fc2b84586 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfig.java @@ -25,44 +25,44 @@ public class LwM2MServerSecurityConfig { @ApiModelProperty(position = 1, value = "Server short Id. Used as link to associate server Object Instance. This identifier uniquely identifies each LwM2M Server configured for the LwM2M Client. " + "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.", example = "123", readOnly = true) + "The values ID:0 and ID:65535 values MUST NOT be used for identifying the LwM2M Server.", example = "123", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected Integer shortServerId = 123; /** Security -> ObjectId = 0 'LWM2M Security' */ @ApiModelProperty(position = 2, value = "Is Bootstrap Server or Lwm2m Server. " + "The LwM2M Client MAY be configured to use one or more LwM2M Server Account(s). " + "The LwM2M Client MUST have at most one LwM2M Bootstrap-Server Account. " + - "(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", readOnly = true) + "(*) The LwM2M client MUST have at least one LwM2M server account after completing the boot sequence specified.", example = "true or false", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected boolean bootstrapServerIs = false; - @ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", readOnly = true) + @ApiModelProperty(position = 3, value = "Host for 'No Security' mode", example = "0.0.0.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected String host; - @ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", readOnly = true) + @ApiModelProperty(position = 4, value = "Port for Lwm2m Server: 'No Security' mode: Lwm2m Server or Bootstrap Server", example = "'5685' or '5687'", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected Integer port; - @ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", readOnly = true) + @ApiModelProperty(position = 7, value = "Client Hold Off Time. The number of seconds to wait before initiating a Client Initiated Bootstrap once the LwM2M Client has determined it should initiate this bootstrap mode. (This information is relevant for use with a Bootstrap-Server only.)", example = "1", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected Integer clientHoldOffTime = 1; @ApiModelProperty(position = 8, value = "Server Public Key for 'Security' mode (DTLS): RPK or X509. Format: base64 encoded", example = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEAZ0pSaGKHk/GrDaUDnQZpeEdGwX7m3Ws+U/kiVat\n" + - "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", readOnly = true) + "+44sgk3c8g0LotfMpLlZJPhPwJ6ipXV+O1r7IZUjBs3LNA==", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected String serverPublicKey; @ApiModelProperty(position = 9, value = "Server Public Key for 'Security' mode (DTLS): X509. Format: base64 encoded", example = "MMIICODCCAd6gAwIBAgIUI88U1zowOdrxDK/dOV+36gJxI2MwCgYIKoZIzj0EAwIwejELMAkGA1UEBhMCVUs\n" + "xEjAQBgNVBAgTCUt5aXYgY2l0eTENMAsGA1UEBxMES3lpdjEUMBIGA1UEChMLVGhpbmdzYm9hcmQxFzAVBgNVBAsMDkRFVkVMT1BFUl9URVNUMRkwFwYDVQQDDBBpbnRlcm1lZGlhdGVfY2EwMB4XDTIyMDEwOTEzMDMwMFoXDTI3MDEwODEzMDMwMFowFDESMBAGA1UEAxM\n" + "JbG9jYWxob3N0MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEUO3vBo/JTv0eooY7XHiKAIVDoWKFqtrU7C6q8AIKqpLcqhCdW+haFeBOH3PjY6EwaWkY04Bir4oanU0s7tz2uKOBpzCBpDAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/\n" + "BAIwADAdBgNVHQ4EFgQUEjc3Q4a0TxzP/3x3EV4fHxYUg0YwHwYDVR0jBBgwFoAUuSquGycMU6Q0SYNcbtSkSD3TfH0wLwYDVR0RBCgwJoIVbG9jYWxob3N0LmxvY2FsZG9tYWlugglsb2NhbGhvc3SCAiAtMAoGCCqGSM49BAMCA0gAMEUCIQD7dbZObyUaoDiNbX+9fUNp\n" + - "AWrD7N7XuJUwZ9FcN75R3gIgb2RNjDkHoyUyF1YajwkBk+7XmIXNClmizNJigj908mw=", readOnly = true) + "AWrD7N7XuJUwZ9FcN75R3gIgb2RNjDkHoyUyF1YajwkBk+7XmIXNClmizNJigj908mw=", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected String serverCertificate; - @ApiModelProperty(position = 10, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", readOnly = true) + @ApiModelProperty(position = 10, value = "Bootstrap Server Account Timeout (If the value is set to 0, or if this resource is not instantiated, the Bootstrap-Server Account lifetime is infinite.)", example = "0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) Integer bootstrapServerAccountTimeout = 0; /** Config -> ObjectId = 1 'LwM2M Server' */ - @ApiModelProperty(position = 11, value = "Specify the lifetime of the registration in seconds.", example = "300", readOnly = true) + @ApiModelProperty(position = 11, value = "Specify the lifetime of the registration in seconds.", example = "300", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private Integer lifetime = 300; @ApiModelProperty(position = 12, value = "The default value the LwM2M Client should use for the Minimum Period of an Observation in the absence of this parameter being included in an Observation. " + - "If this Resource doesn’t exist, the default value is 0.", example = "1", readOnly = true) + "If this Resource doesn’t exist, the default value is 0.", example = "1", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private Integer defaultMinPeriod = 1; /** ResourceID=6 'Notification Storing When Disabled or Offline' */ @ApiModelProperty(position = 13, value = "If true, the LwM2M Client stores “Notify” operations to the LwM2M Server while the LwM2M Server account is disabled or the LwM2M Client is offline. After the LwM2M Server account is enabled or the LwM2M Client is online, the LwM2M Client reports the stored “Notify” operations to the Server. " + "If false, the LwM2M Client discards all the “Notify” operations or temporarily disables the Observe function while the LwM2M Server is disabled or the LwM2M Client is offline. " + - "The default value is true.", example = "true", readOnly = true) + "The default value is true.", example = "true", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private boolean notifIfDisabled = true; @ApiModelProperty(position = 14, value = "This Resource defines the transport binding configured for the LwM2M Client. " + - "If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", readOnly = true) + "If the LwM2M Client supports the binding specified in this Resource, the LwM2M Client MUST use that transport for the Current Binding Mode.", example = "U", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String binding = "U"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java index ce2686f086..948a492e42 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/lwm2m/bootstrap/LwM2MServerSecurityConfigDefault.java @@ -22,8 +22,8 @@ import lombok.Data; @ApiModel @Data public class LwM2MServerSecurityConfigDefault extends LwM2MServerSecurityConfig { - @ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", readOnly = true) + @ApiModelProperty(position = 5, value = "Host for 'Security' mode (DTLS)", example = "0.0.0.0", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected String securityHost; - @ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", readOnly = true) + @ApiModelProperty(position = 6, value = "Port for 'Security' mode (DTLS): Lwm2m Server or Bootstrap Server", example = "5686 or 5688", accessMode = ApiModelProperty.AccessMode.READ_ONLY) protected Integer securityPort; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java index 4a4cd1afc4..58c23d0877 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java @@ -98,25 +98,25 @@ public class Edge extends SearchTextBasedWithAdditionalInfo implements H return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the edge creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the edge creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); } - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public TenantId getTenantId() { return this.tenantId; } - @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEdgeToCustomer' to change the Customer Id.", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignEdgeToCustomer' to change the Customer Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public CustomerId getCustomerId() { return this.customerId; } - @ApiModelProperty(position = 5, value = "JSON object with Root Rule Chain Id. Use 'setEdgeRootRuleChain' to change the Root Rule Chain Id.", readOnly = true) + @ApiModelProperty(position = 5, value = "JSON object with Root Rule Chain Id. Use 'setEdgeRootRuleChain' to change the Root Rule Chain Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public RuleChainId getRootRuleChainId() { return this.rootRuleChainId; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java index 3b1489d05f..660fa945ca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEventType.java @@ -31,5 +31,7 @@ public enum EdgeEventType { TENANT, WIDGETS_BUNDLE, WIDGET_TYPE, - ADMIN_SETTINGS + ADMIN_SETTINGS, + OTA_PACKAGE, + QUEUE } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 8bd13b7c3f..f8bc8fc089 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -107,6 +107,10 @@ public class EntityIdFactory { return new WidgetsBundleId(uuid); case WIDGET_TYPE: return new WidgetTypeId(uuid); + case OTA_PACKAGE: + return new OtaPackageId(uuid); + case QUEUE: + return new QueueId(uuid); case EDGE: return new EdgeId(uuid); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java index cfe6918a7d..4682c8f95b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/IdBased.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonSetter; import java.io.Serializable; import java.util.UUID; @@ -33,6 +34,7 @@ public abstract class IdBased implements HasId { this.id = id; } + @JsonSetter public void setId(I id) { this.id = id; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java index 02f6683989..511e9fe170 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java @@ -48,22 +48,22 @@ public class PageData { this.hasNext = hasNext; } - @ApiModelProperty(position = 1, value = "Array of the entities", readOnly = true) + @ApiModelProperty(position = 1, value = "Array of the entities", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public List getData() { return data; } - @ApiModelProperty(position = 2, value = "Total number of available pages. Calculated based on the 'pageSize' request parameter and total number of entities that match search criteria", readOnly = true) + @ApiModelProperty(position = 2, value = "Total number of available pages. Calculated based on the 'pageSize' request parameter and total number of entities that match search criteria", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public int getTotalPages() { return totalPages; } - @ApiModelProperty(position = 3, value = "Total number of elements in all available pages", readOnly = true) + @ApiModelProperty(position = 3, value = "Total number of elements in all available pages", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public long getTotalElements() { return totalElements; } - @ApiModelProperty(position = 4, value = "'false' value indicates the end of the result set", readOnly = true) + @ApiModelProperty(position = 4, value = "'false' value indicates the end of the result set", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @JsonProperty("hasNext") public boolean hasNext() { return hasNext; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java index cdc5d55154..81708ae101 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java @@ -32,19 +32,19 @@ public class ComponentDescriptor extends SearchTextBased private static final long serialVersionUID = 1L; - @ApiModelProperty(position = 3, value = "Type of the Rule Node", readOnly = true) + @ApiModelProperty(position = 3, value = "Type of the Rule Node", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Getter @Setter private ComponentType type; - @ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", readOnly = true, allowableValues = "TENANT", example = "TENANT") + @ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, allowableValues = "TENANT", example = "TENANT") @Getter @Setter private ComponentScope scope; @Length(fieldName = "name") - @ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", readOnly = true, example = "Custom Rule Node") + @ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "Custom Rule Node") @Getter @Setter private String name; - @ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", readOnly = true, example = "com.mycompany.CustomRuleNode") + @ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "com.mycompany.CustomRuleNode") @Getter @Setter private String clazz; - @ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", readOnly = true) + @ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Getter @Setter private transient JsonNode configurationDescriptor; @Length(fieldName = "actions") - @ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", readOnly = true) + @ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Getter @Setter private String actions; public ComponentDescriptor() { @@ -74,7 +74,7 @@ public class ComponentDescriptor extends SearchTextBased return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the descriptor creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the descriptor creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java b/common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java index b4d65706a2..a6623be8a4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/queue/Queue.java @@ -22,11 +22,17 @@ import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.QueueId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; +import org.thingsboard.server.common.data.validation.Length; +import org.thingsboard.server.common.data.validation.NoXss; @Data public class Queue extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { private TenantId tenantId; + @NoXss + @Length(fieldName = "name") private String name; + @NoXss + @Length(fieldName = "topic") private String topic; private int pollInterval; private int partitions; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java index d57b1aa9d7..8c7f2291cc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelation.java @@ -73,7 +73,7 @@ public class EntityRelation implements Serializable { this.additionalInfo = entityRelation.getAdditionalInfo(); } - @ApiModelProperty(position = 1, value = "JSON object with [from] Entity Id.", readOnly = true) + @ApiModelProperty(position = 1, value = "JSON object with [from] Entity Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public EntityId getFrom() { return from; } @@ -82,7 +82,7 @@ public class EntityRelation implements Serializable { this.from = from; } - @ApiModelProperty(position = 2, value = "JSON object with [to] Entity Id.", readOnly = true) + @ApiModelProperty(position = 2, value = "JSON object with [to] Entity Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) public EntityId getTo() { return to; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java index 42f57b57a2..95714351e1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/EntityRelationInfo.java @@ -32,7 +32,7 @@ public class EntityRelationInfo extends EntityRelation { super(entityRelation); } - @ApiModelProperty(position = 6, value = "Name of the entity for [from] direction.", readOnly = true, example = "A4B72CCDFF33") + @ApiModelProperty(position = 6, value = "Name of the entity for [from] direction.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "A4B72CCDFF33") public String getFromName() { return fromName; } @@ -41,7 +41,7 @@ public class EntityRelationInfo extends EntityRelation { this.fromName = fromName; } - @ApiModelProperty(position = 7, value = "Name of the entity for [to] direction.", readOnly = true, example = "A4B72CCDFF35") + @ApiModelProperty(position = 7, value = "Name of the entity for [to] direction.", accessMode = ApiModelProperty.AccessMode.READ_ONLY, example = "A4B72CCDFF35") public String getToName() { return toName; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java index ee2ea6d830..7703c1aec2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java @@ -31,19 +31,19 @@ import org.thingsboard.server.common.data.id.TenantId; @EqualsAndHashCode(callSuper = true) public class Rpc extends BaseData implements HasTenantId { - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; - @ApiModelProperty(position = 4, value = "JSON object with Device Id.", readOnly = true) + @ApiModelProperty(position = 4, value = "JSON object with Device Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private DeviceId deviceId; - @ApiModelProperty(position = 5, value = "Expiration time of the request.", readOnly = true) + @ApiModelProperty(position = 5, value = "Expiration time of the request.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private long expirationTime; - @ApiModelProperty(position = 6, value = "The request body that will be used to send message to device.", readOnly = true) + @ApiModelProperty(position = 6, value = "The request body that will be used to send message to device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private JsonNode request; - @ApiModelProperty(position = 7, value = "The response from the device.", readOnly = true) + @ApiModelProperty(position = 7, value = "The response from the device.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private JsonNode response; - @ApiModelProperty(position = 8, value = "The current status of the RPC call.", readOnly = true) + @ApiModelProperty(position = 8, value = "The current status of the RPC call.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private RpcStatus status; - @ApiModelProperty(position = 9, value = "Additional info used in the rule engine to process the updates to the RPC state.", readOnly = true) + @ApiModelProperty(position = 9, value = "Additional info used in the rule engine to process the updates to the RPC state.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private JsonNode additionalInfo; public Rpc() { @@ -71,7 +71,7 @@ public class Rpc extends BaseData implements HasTenantId { return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the rpc creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the rpc creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java index 17d7ffef0b..a8ba432ceb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChain.java @@ -22,6 +22,7 @@ import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; @@ -35,11 +36,11 @@ import org.thingsboard.server.common.data.validation.NoXss; @Data @EqualsAndHashCode(callSuper = true) @Slf4j -public class RuleChain extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { +public class RuleChain extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, ExportableEntity { private static final long serialVersionUID = -5656679015121935465L; - @ApiModelProperty(position = 3, required = true, value = "JSON object with Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, required = true, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "name") @@ -56,6 +57,8 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im @ApiModelProperty(position = 9, value = "Reserved for future usage. The actual list of rule nodes and their relations is stored in the database separately.") private transient JsonNode configuration; + private RuleChainId externalId; + @JsonIgnore private byte[] configurationBytes; @@ -75,6 +78,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im this.firstRuleNodeId = ruleChain.getFirstRuleNodeId(); this.root = ruleChain.isRoot(); this.setConfiguration(ruleChain.getConfiguration()); + this.setExternalId(ruleChain.getExternalId()); } @Override @@ -96,7 +100,7 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo im return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the rule chain creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the rule chain creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java index 93fd690d6d..885226caba 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainData.java @@ -25,8 +25,8 @@ import java.util.List; @Data public class RuleChainData { - @ApiModelProperty(position = 1, required = true, value = "List of the Rule Chain objects.", readOnly = true) + @ApiModelProperty(position = 1, required = true, value = "List of the Rule Chain objects.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) List ruleChains; - @ApiModelProperty(position = 2, required = true, value = "List of the Rule Chain metadata objects.", readOnly = true) + @ApiModelProperty(position = 2, required = true, value = "List of the Rule Chain metadata objects.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) List metadata; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java index 7bd19e2441..fcb88924da 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java @@ -33,7 +33,7 @@ import java.util.Map; @Data public class RuleChainMetaData { - @ApiModelProperty(position = 1, required = true, value = "JSON object with Rule Chain Id.", readOnly = true) + @ApiModelProperty(position = 1, required = true, value = "JSON object with Rule Chain Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private RuleChainId ruleChainId; @ApiModelProperty(position = 2, required = true, value = "Index of the first rule node in the 'nodes' list") diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java index fa2760dbc3..c9bf8b462c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java @@ -37,7 +37,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo impl private static final long serialVersionUID = -5656679015121235465L; - @ApiModelProperty(position = 3, value = "JSON object with the Rule Chain Id. ", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with the Rule Chain Id. ", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private RuleChainId ruleChainId; @Length(fieldName = "type") @ApiModelProperty(position = 4, value = "Full Java Class Name of the rule node implementation. ", example = "com.mycompany.iot.rule.engine.ProcessingNode") @@ -53,6 +53,8 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo impl @JsonIgnore private byte[] configurationBytes; + private RuleNodeId externalId; + public RuleNode() { super(); } @@ -68,6 +70,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo impl this.name = ruleNode.getName(); this.debugMode = ruleNode.isDebugMode(); this.setConfiguration(ruleNode.getConfiguration()); + this.externalId = ruleNode.getExternalId(); } @Override @@ -97,7 +100,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo impl return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the rule node creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the rule node creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index ed797eec70..e5af27776c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java @@ -48,7 +48,7 @@ public class DeviceCredentials extends BaseData implements this.credentialsValue = deviceCredentials.getCredentialsValue(); } - @ApiModelProperty(position = 1, required = true, readOnly = true, value = "The Id is automatically generated during device creation. " + + @ApiModelProperty(position = 1, required = true, accessMode = ApiModelProperty.AccessMode.READ_ONLY, value = "The Id is automatically generated during device creation. " + "Use 'getDeviceCredentialsByDeviceId' to obtain the id based on device id. " + "Use 'updateDeviceCredentials' to update device credentials. ", example = "784f394c-42b6-435a-983c-b7beff2784f9") @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java index ec19089cab..2dcd95f435 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java @@ -15,16 +15,17 @@ */ package org.thingsboard.server.common.data.security.event; +import lombok.Data; import org.thingsboard.server.common.data.id.UserId; +@Data public class UserAuthDataChangedEvent { private final UserId userId; + private final long ts; public UserAuthDataChangedEvent(UserId userId) { this.userId = userId; + this.ts = System.currentTimeMillis(); } - public UserId getUserId() { - return userId; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java new file mode 100644 index 0000000000..0e2929bef8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.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.common.data.sync; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.widget.WidgetsBundle; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +@JacksonAnnotationsInside +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", include = JsonTypeInfo.As.EXTERNAL_PROPERTY) +@JsonSubTypes({ + @Type(name = "DEVICE", value = Device.class), + @Type(name = "RULE_CHAIN", value = RuleChain.class), + @Type(name = "DEVICE_PROFILE", value = DeviceProfile.class), + @Type(name = "ASSET", value = Asset.class), + @Type(name = "DASHBOARD", value = Dashboard.class), + @Type(name = "CUSTOMER", value = Customer.class), + @Type(name = "ENTITY_VIEW", value = EntityView.class), + @Type(name = "WIDGETS_BUNDLE", value = WidgetsBundle.class) +}) +public @interface JsonTbEntity { +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ThrowingRunnable.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ThrowingRunnable.java new file mode 100644 index 0000000000..1cb2ac8c74 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ThrowingRunnable.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.common.data.sync; + +import org.thingsboard.server.common.data.exception.ThingsboardException; + +public interface ThrowingRunnable { + + void run() throws ThingsboardException; + + default ThrowingRunnable andThen(ThrowingRunnable after) { + return () -> { + this.run(); + after.run(); + }; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/AttributeExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/AttributeExportData.java new file mode 100644 index 0000000000..dcf69c0877 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/AttributeExportData.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.common.data.sync.ie; + +import lombok.Data; + +@Data +public class AttributeExportData { + private String key; + private Long lastUpdateTs; + + private Boolean booleanValue; + private String strValue; + private Long longValue; + private Double doubleValue; + private String jsonValue; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java new file mode 100644 index 0000000000..27d16899d1 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/DeviceExportData.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.ie; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.security.DeviceCredentials; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Data +public class DeviceExportData extends EntityExportData { + + @JsonProperty(index = 3) + @JsonIgnoreProperties({"id", "deviceId", "createdTime"}) + private DeviceCredentials credentials; + + @JsonIgnore + @Override + public boolean hasCredentials() { + return credentials != null; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java new file mode 100644 index 0000000000..afe6c925c7 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.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.common.data.sync.ie; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; +import lombok.Data; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.sync.JsonTbEntity; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "entityType", include = As.EXISTING_PROPERTY, visible = true, defaultImpl = EntityExportData.class) +@JsonSubTypes({ + @Type(name = "DEVICE", value = DeviceExportData.class), + @Type(name = "RULE_CHAIN", value = RuleChainExportData.class), + @Type(name = "WIDGETS_BUNDLE", value = WidgetsBundleExportData.class) +}) +@JsonInclude(JsonInclude.Include.NON_NULL) +@Data +public class EntityExportData> { + + public static final Comparator relationsComparator = Comparator + .comparing(EntityRelation::getFrom, Comparator.comparing(EntityId::getId)) + .thenComparing(EntityRelation::getTo, Comparator.comparing(EntityId::getId)) + .thenComparing(EntityRelation::getTypeGroup) + .thenComparing(EntityRelation::getType); + + public static final Comparator attrComparator = Comparator + .comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs); + + @JsonProperty(index = 2) + @JsonIgnoreProperties({"tenantId", "createdTime"}) + @JsonTbEntity + private E entity; + @JsonProperty(index = 1) + private EntityType entityType; + + @JsonProperty(index = 100) + private List relations; + @JsonProperty(index = 101) + private Map> attributes; + + public EntityExportData sort() { + if (relations != null && !relations.isEmpty()) { + relations.sort(relationsComparator); + } + if (attributes != null && !attributes.isEmpty()) { + attributes.values().forEach(list -> list.sort(attrComparator)); + } + return this; + } + + @JsonIgnore + public EntityId getExternalId() { + return entity.getExternalId() != null ? entity.getExternalId() : entity.getId(); + } + + @JsonIgnore + public boolean hasCredentials() { + return false; + } + + @JsonIgnore + public boolean hasAttributes() { + return attributes != null; + } + + @JsonIgnore + public boolean hasRelations() { + return relations != null; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportSettings.java new file mode 100644 index 0000000000..1a625640e0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportSettings.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.common.data.sync.ie; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EntityExportSettings { + private boolean exportRelations; + private boolean exportAttributes; + private boolean exportCredentials; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportResult.java new file mode 100644 index 0000000000..1c6f4c060a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportResult.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.ie; + +import lombok.Data; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.sync.ThrowingRunnable; + +@Data +public class EntityImportResult> { + + private E savedEntity; + private E oldEntity; + private EntityType entityType; + + private ThrowingRunnable saveReferencesCallback = () -> {}; + private ThrowingRunnable sendEventsCallback = () -> {}; + + private boolean updatedAllExternalIds = true; + + private boolean created; + private boolean updated; + private boolean updatedRelatedEntities; + + public void addSaveReferencesCallback(ThrowingRunnable callback) { + this.saveReferencesCallback = this.saveReferencesCallback.andThen(callback); + } + + public void addSendEventsCallback(ThrowingRunnable callback) { + this.sendEventsCallback = this.sendEventsCallback.andThen(callback); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportSettings.java new file mode 100644 index 0000000000..9b95b8eca2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityImportSettings.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.ie; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EntityImportSettings { + private boolean findExistingByName; + private boolean updateRelations; + private boolean saveAttributes; + private boolean saveCredentials; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java new file mode 100644 index 0000000000..5c37aa35b6 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.ie; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Data +public class RuleChainExportData extends EntityExportData { + + @JsonProperty(index = 3) + @JsonIgnoreProperties("ruleChainId") + private RuleChainMetaData metaData; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java new file mode 100644 index 0000000000..aa7600a48d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.ie; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.widget.BaseWidgetType; +import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetsBundle; + +import java.util.Comparator; +import java.util.List; + +@Data +@EqualsAndHashCode(callSuper = true) +public class WidgetsBundleExportData extends EntityExportData { + + @JsonProperty(index = 3) + private List widgets; + + @Override + public EntityExportData sort() { + super.sort(); + widgets.sort(Comparator.comparing(BaseWidgetType::getAlias)); + return this; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java similarity index 97% rename from application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java index 96075eb4c8..a0cd40251f 100644 --- a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportColumnType.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.importing; +package org.thingsboard.server.common.data.sync.ie.importing.csv; import lombok.Getter; import org.thingsboard.server.common.data.DataConstants; diff --git a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportRequest.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/importing/BulkImportRequest.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportRequest.java index 9f8195ca45..8d7b6f9d82 100644 --- a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.importing; +package org.thingsboard.server.common.data.sync.ie.importing.csv; import lombok.Data; diff --git a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportResult.java similarity index 94% rename from application/src/main/java/org/thingsboard/server/service/importing/BulkImportResult.java rename to common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportResult.java index 651aedeb0b..cced74afaa 100644 --- a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportResult.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/importing/csv/BulkImportResult.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.importing; +package org.thingsboard.server.common.data.sync.ie.importing.csv; import lombok.Data; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/AutoCommitSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/AutoCommitSettings.java new file mode 100644 index 0000000000..2120ec4c44 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/AutoCommitSettings.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.sync.vc.request.create.AutoVersionCreateConfig; + +import java.util.HashMap; + +public class AutoCommitSettings extends HashMap { + + private static final long serialVersionUID = -5757067601838792059L; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/BranchInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/BranchInfo.java new file mode 100644 index 0000000000..c4dc3d8e07 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/BranchInfo.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.Objects; + +@Data +public class BranchInfo { + private final String name; + + @JsonProperty("default") + private final boolean isDefault; + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + BranchInfo that = (BranchInfo) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataDiff.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataDiff.java new file mode 100644 index 0000000000..dae2b99106 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataDiff.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; + +@Data +@AllArgsConstructor +public class EntityDataDiff { + private EntityExportData currentVersion; + private EntityExportData otherVersion; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java new file mode 100644 index 0000000000..2529ec8af6 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityDataInfo.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EntityDataInfo { + boolean hasRelations; + boolean hasAttributes; + boolean hasCredentials; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java new file mode 100644 index 0000000000..cbfe9f6457 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityLoadError.java @@ -0,0 +1,55 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Data; +import org.apache.commons.lang3.ClassUtils; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.id.EntityId; + +import java.io.Serializable; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class EntityLoadError implements Serializable { + + private static final long serialVersionUID = 7538450180582109391L; + + private String type; + private EntityId source; + private EntityId target; + private String message; + + public static EntityLoadError credentialsError(EntityId sourceId) { + return EntityLoadError.builder().type("DEVICE_CREDENTIALS_CONFLICT").source(sourceId).build(); + } + + public static EntityLoadError referenceEntityError(EntityId sourceId, EntityId targetId) { + return EntityLoadError.builder().type("MISSING_REFERENCED_ENTITY").source(sourceId).target(targetId).build(); + } + + public static EntityLoadError runtimeError(Throwable e) { + String message = e.getMessage(); + if (StringUtils.isEmpty(message)) { + message = "unexpected error (" + ClassUtils.getShortClassName(e.getClass()) + ")"; + } + return EntityLoadError.builder().type("RUNTIME").message(message).build(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityTypeLoadResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityTypeLoadResult.java new file mode 100644 index 0000000000..06df7ebb83 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityTypeLoadResult.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.EntityType; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EntityTypeLoadResult implements Serializable { + private static final long serialVersionUID = -8428039809651395241L; + + private EntityType entityType; + private int created; + private int updated; + private int deleted; + + public EntityTypeLoadResult(EntityType entityType) { + this.entityType = entityType; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java new file mode 100644 index 0000000000..e7ef81a562 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersion.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class EntityVersion implements Serializable { + + private static final long serialVersionUID = -3705022663019175258L; + + private long timestamp; + private String id; + private String name; + private String author; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersionsDiff.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersionsDiff.java new file mode 100644 index 0000000000..2104472047 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/EntityVersionsDiff.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.sync.ie.EntityExportData; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class EntityVersionsDiff { + private EntityId externalId; + private EntityExportData entityDataAtVersion1; + private EntityExportData entityDataAtVersion2; + private String rawDiff; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositoryAuthMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositoryAuthMethod.java new file mode 100644 index 0000000000..3c0e5849a3 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositoryAuthMethod.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +public enum RepositoryAuthMethod { + USERNAME_PASSWORD, + PRIVATE_KEY +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java new file mode 100644 index 0000000000..50e23a1dba --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.io.Serializable; + +@Data +public class RepositorySettings implements Serializable { + private static final long serialVersionUID = -3211552851889198721L; + + private String repositoryUri; + private RepositoryAuthMethod authMethod; + private String username; + private String password; + private String privateKeyFileName; + private String privateKey; + private String privateKeyPassword; + private String defaultBranch; + + public RepositorySettings() { + } + + public RepositorySettings(RepositorySettings settings) { + this.repositoryUri = settings.getRepositoryUri(); + this.authMethod = settings.getAuthMethod(); + this.username = settings.getUsername(); + this.password = settings.getPassword(); + this.privateKeyFileName = settings.getPrivateKeyFileName(); + this.privateKey = settings.getPrivateKey(); + this.privateKeyPassword = settings.getPrivateKeyPassword(); + this.defaultBranch = settings.getDefaultBranch(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionCreationResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionCreationResult.java new file mode 100644 index 0000000000..608cc5ddd3 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionCreationResult.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class VersionCreationResult implements Serializable { + private static final long serialVersionUID = 8032189124530267838L; + + private EntityVersion version; + private int added; + private int modified; + private int removed; + + private String error; + private boolean done; + + + public VersionCreationResult(EntityVersion version, int added, int modified, int removed) { + this.version = version; + this.added = added; + this.modified = modified; + this.removed = removed; + this.done = true; + } + + public VersionCreationResult(String error) { + this.error = error; + this.done = true; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionLoadResult.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionLoadResult.java new file mode 100644 index 0000000000..05d96f10ce --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionLoadResult.java @@ -0,0 +1,53 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Data; + +import java.io.Serializable; +import java.util.Collections; +import java.util.List; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class VersionLoadResult implements Serializable { + + private static final long serialVersionUID = -1386093599856747449L; + + private List result; + private EntityLoadError error; + private boolean done; + + public static VersionLoadResult empty() { + return VersionLoadResult.builder().result(Collections.emptyList()).build(); + } + + public static VersionLoadResult success(List result) { + return VersionLoadResult.builder().result(result).build(); + } + + public static VersionLoadResult success(EntityTypeLoadResult result) { + return VersionLoadResult.builder().result(List.of(result)).build(); + } + + public static VersionLoadResult error(EntityLoadError error) { + return VersionLoadResult.builder().error(error).done(true).build(); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionedEntityInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionedEntityInfo.java new file mode 100644 index 0000000000..ce8c91f4ae --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/VersionedEntityInfo.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.common.data.sync.vc; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.id.EntityId; + +@Data +@NoArgsConstructor +public class VersionedEntityInfo { + private EntityId externalId; + + public VersionedEntityInfo(EntityId externalId) { + this.externalId = externalId; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/AutoVersionCreateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/AutoVersionCreateConfig.java new file mode 100644 index 0000000000..084b2a1059 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/AutoVersionCreateConfig.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.create; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +@Data +public class AutoVersionCreateConfig extends VersionCreateConfig { + + private static final long serialVersionUID = 8245450889383315551L; + + private String branch; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/ComplexVersionCreateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/ComplexVersionCreateRequest.java new file mode 100644 index 0000000000..f8d7279f40 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/ComplexVersionCreateRequest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.create; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.EntityType; + +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = true) +public class ComplexVersionCreateRequest extends VersionCreateRequest { + + // Default sync strategy + private SyncStrategy syncStrategy; + private Map entityTypes; + + @Override + public VersionCreateRequestType getType() { + return VersionCreateRequestType.COMPLEX; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/EntityTypeVersionCreateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/EntityTypeVersionCreateConfig.java new file mode 100644 index 0000000000..92cab96354 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/EntityTypeVersionCreateConfig.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.create; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +import java.util.List; +import java.util.UUID; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EntityTypeVersionCreateConfig extends VersionCreateConfig { + + //optional + private SyncStrategy syncStrategy; + private List entityIds; + private boolean allEntities; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/SingleEntityVersionCreateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/SingleEntityVersionCreateRequest.java new file mode 100644 index 0000000000..507afd116e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/SingleEntityVersionCreateRequest.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.create; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.EntityId; + +@Data +@EqualsAndHashCode(callSuper = true) +public class SingleEntityVersionCreateRequest extends VersionCreateRequest { + + private EntityId entityId; + private VersionCreateConfig config; + + @Override + public VersionCreateRequestType getType() { + return VersionCreateRequestType.SINGLE_ENTITY; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/SyncStrategy.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/SyncStrategy.java new file mode 100644 index 0000000000..baf24efeb8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/SyncStrategy.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.create; + +public enum SyncStrategy { + MERGE, + OVERWRITE +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateConfig.java new file mode 100644 index 0000000000..86154bdfa0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateConfig.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.create; + +import lombok.Data; + +import java.io.Serializable; + +@Data +public class VersionCreateConfig implements Serializable { + private static final long serialVersionUID = 1223723167716612772L; + + private boolean saveRelations; + private boolean saveAttributes; + private boolean saveCredentials; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateRequest.java new file mode 100644 index 0000000000..9a23838921 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateRequest.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.common.data.sync.vc.request.create; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @Type(name = "SINGLE_ENTITY", value = SingleEntityVersionCreateRequest.class), + @Type(name = "COMPLEX", value = ComplexVersionCreateRequest.class) +}) +@Data +public abstract class VersionCreateRequest { + + private String versionName; + private String branch; + + public abstract VersionCreateRequestType getType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateRequestType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateRequestType.java new file mode 100644 index 0000000000..d57363cf7d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/create/VersionCreateRequestType.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.create; + +public enum VersionCreateRequestType { + SINGLE_ENTITY, + COMPLEX +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/EntityTypeVersionLoadConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/EntityTypeVersionLoadConfig.java new file mode 100644 index 0000000000..ffb1f25d3c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/EntityTypeVersionLoadConfig.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.load; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EntityTypeVersionLoadConfig extends VersionLoadConfig { + + private boolean removeOtherEntities; + private boolean findExistingEntityByName; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/EntityTypeVersionLoadRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/EntityTypeVersionLoadRequest.java new file mode 100644 index 0000000000..f2cb83f0d4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/EntityTypeVersionLoadRequest.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.load; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.EntityType; + +import java.util.Map; + +@Data +@EqualsAndHashCode(callSuper = true) +public class EntityTypeVersionLoadRequest extends VersionLoadRequest { + + private Map entityTypes; + + @Override + public VersionLoadRequestType getType() { + return VersionLoadRequestType.ENTITY_TYPE; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/SingleEntityVersionLoadRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/SingleEntityVersionLoadRequest.java new file mode 100644 index 0000000000..cf31317b4a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/SingleEntityVersionLoadRequest.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.load; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.EntityId; + +@Data +@EqualsAndHashCode(callSuper = true) +public class SingleEntityVersionLoadRequest extends VersionLoadRequest { + + private EntityId externalEntityId; + + private VersionLoadConfig config; + + @Override + public VersionLoadRequestType getType() { + return VersionLoadRequestType.SINGLE_ENTITY; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadConfig.java new file mode 100644 index 0000000000..a27cf06538 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadConfig.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.load; + +import lombok.Data; + +@Data +public class VersionLoadConfig { + + private boolean loadRelations; + private boolean loadAttributes; + private boolean loadCredentials; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequest.java new file mode 100644 index 0000000000..cb60d91b24 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequest.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.common.data.sync.vc.request.load; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import lombok.Data; + +import static com.fasterxml.jackson.annotation.JsonSubTypes.Type; + +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +@JsonSubTypes({ + @Type(name = "SINGLE_ENTITY", value = SingleEntityVersionLoadRequest.class), + @Type(name = "ENTITY_TYPE", value = EntityTypeVersionLoadRequest.class) +}) +@Data +public abstract class VersionLoadRequest { + + private String versionId; + + public abstract VersionLoadRequestType getType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequestType.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequestType.java new file mode 100644 index 0000000000..190e6871d5 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/request/load/VersionLoadRequestType.java @@ -0,0 +1,21 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc.request.load; + +public enum VersionLoadRequestType { + SINGLE_ENTITY, + ENTITY_TYPE +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 15eb429805..e7d6383540 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.common.data.tenant.profile; -import io.swagger.annotations.ApiModel; -import io.swagger.annotations.ApiModelProperty; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -46,6 +44,9 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private String transportDeviceTelemetryMsgRateLimit; private String transportDeviceTelemetryDataPointsRateLimit; + private String tenantEntityExportRateLimit; + private String tenantEntityImportRateLimit; + private long maxTransportMessages; private long maxTransportDataPoints; private long maxREExecutions; @@ -56,6 +57,22 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxSms; private long maxCreatedAlarms; + private String tenantServerRestLimitsConfiguration; + private String customerServerRestLimitsConfiguration; + + private int maxWsSessionsPerTenant; + private int maxWsSessionsPerCustomer; + private int maxWsSessionsPerRegularUser; + private int maxWsSessionsPerPublicUser; + private int wsMsgQueueLimitPerSession; + private long maxWsSubscriptionsPerTenant; + private long maxWsSubscriptionsPerCustomer; + private long maxWsSubscriptionsPerRegularUser; + private long maxWsSubscriptionsPerPublicUser; + private String wsUpdatesPerSessionRateLimit; + + private String cassandraQueryTenantRateLimitsConfiguration; + private int defaultStorageTtlDays; private int alarmsTtlDays; private int rpcTtlDays; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java index 0e88a6535d..be5014a4cd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java @@ -29,19 +29,19 @@ public class BaseWidgetType extends BaseData implements HasTenantI private static final long serialVersionUID = 8388684344603660756L; - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "bundleAlias") - @ApiModelProperty(position = 4, value = "Reference to widget bundle", readOnly = true) + @ApiModelProperty(position = 4, value = "Reference to widget bundle", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String bundleAlias; @NoXss @Length(fieldName = "alias") - @ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", readOnly = true) + @ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String alias; @NoXss @Length(fieldName = "name") - @ApiModelProperty(position = 6, value = "Widget name used in search and UI", readOnly = true) + @ApiModelProperty(position = 6, value = "Widget name used in search and UI", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String name; public BaseWidgetType() { @@ -69,7 +69,7 @@ public class BaseWidgetType extends BaseData implements HasTenantI return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the Widget Type creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the Widget Type creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java index 96c5512dad..6b836c53e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; @Data public class WidgetType extends BaseWidgetType { - @ApiModelProperty(position = 7, value = "Complex JSON object that describes the widget type", readOnly = true) + @ApiModelProperty(position = 7, value = "Complex JSON object that describes the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private transient JsonNode descriptor; public WidgetType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java index 2a46091671..1447400721 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java @@ -27,11 +27,11 @@ import org.thingsboard.server.common.data.validation.NoXss; public class WidgetTypeDetails extends WidgetType { @Length(fieldName = "image", max = 1000000) - @ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", readOnly = true) + @ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String image; @NoXss @Length(fieldName = "description") - @ApiModelProperty(position = 9, value = "Description of the widget", readOnly = true) + @ApiModelProperty(position = 9, value = "Description of the widget", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String description; public WidgetTypeDetails() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java index e6d7f591d2..bb7b19fce6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java @@ -23,13 +23,13 @@ import org.thingsboard.server.common.data.validation.NoXss; @Data public class WidgetTypeInfo extends BaseWidgetType { - @ApiModelProperty(position = 7, value = "Base64 encoded widget thumbnail", readOnly = true) + @ApiModelProperty(position = 7, value = "Base64 encoded widget thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String image; @NoXss - @ApiModelProperty(position = 7, value = "Description of the widget type", readOnly = true) + @ApiModelProperty(position = 7, value = "Description of the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String description; @NoXss - @ApiModelProperty(position = 8, value = "Type of the widget (timeseries, latest, control, alarm or static)", readOnly = true) + @ApiModelProperty(position = 8, value = "Type of the widget (timeseries, latest, control, alarm or static)", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String widgetType; public WidgetTypeInfo() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index 1515aceb62..389771e46f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -15,10 +15,13 @@ */ package org.thingsboard.server.common.data.widget; +import com.fasterxml.jackson.annotation.JsonIgnore; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import org.thingsboard.server.common.data.ExportableEntity; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.TenantId; @@ -27,42 +30,47 @@ import org.thingsboard.server.common.data.validation.Length; import org.thingsboard.server.common.data.validation.NoXss; @ApiModel -public class WidgetsBundle extends SearchTextBased implements HasTenantId { +@EqualsAndHashCode(callSuper = true) +public class WidgetsBundle extends SearchTextBased implements HasTenantId, ExportableEntity { private static final long serialVersionUID = -7627368878362410489L; @Getter @Setter - @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true) + @ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private TenantId tenantId; @NoXss @Length(fieldName = "alias") @Getter @Setter - @ApiModelProperty(position = 4, value = "Unique alias that is used in widget types as a reference widget bundle", readOnly = true) + @ApiModelProperty(position = 4, value = "Unique alias that is used in widget types as a reference widget bundle", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String alias; @NoXss @Length(fieldName = "title") @Getter @Setter - @ApiModelProperty(position = 5, value = "Title used in search and UI", readOnly = true) + @ApiModelProperty(position = 5, value = "Title used in search and UI", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String title; @Length(fieldName = "image", max = 1000000) @Getter @Setter - @ApiModelProperty(position = 6, value = "Base64 encoded thumbnail", readOnly = true) + @ApiModelProperty(position = 6, value = "Base64 encoded thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String image; @NoXss @Length(fieldName = "description") @Getter @Setter - @ApiModelProperty(position = 7, value = "Description", readOnly = true) + @ApiModelProperty(position = 7, value = "Description", accessMode = ApiModelProperty.AccessMode.READ_ONLY) private String description; + @Getter + @Setter + private WidgetsBundleId externalId; + public WidgetsBundle() { super(); } @@ -78,6 +86,7 @@ public class WidgetsBundle extends SearchTextBased implements H this.title = widgetsBundle.getTitle(); this.image = widgetsBundle.getImage(); this.description = widgetsBundle.getDescription(); + this.externalId = widgetsBundle.getExternalId(); } @ApiModelProperty(position = 1, value = "JSON object with the Widget Bundle Id. " + @@ -89,7 +98,7 @@ public class WidgetsBundle extends SearchTextBased implements H return super.getId(); } - @ApiModelProperty(position = 2, value = "Timestamp of the Widget Bundle creation, in milliseconds", example = "1609459200000", readOnly = true) + @ApiModelProperty(position = 2, value = "Timestamp of the Widget Bundle creation, in milliseconds", example = "1609459200000", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override public long getCreatedTime() { return super.getCreatedTime(); @@ -100,31 +109,10 @@ public class WidgetsBundle extends SearchTextBased implements H return getTitle(); } + @JsonIgnore @Override - public int hashCode() { - int result = super.hashCode(); - result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0); - result = 31 * result + (alias != null ? alias.hashCode() : 0); - result = 31 * result + (title != null ? title.hashCode() : 0); - result = 31 * result + (image != null ? image.hashCode() : 0); - result = 31 * result + (description != null ? description.hashCode() : 0); - return result; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - if (!super.equals(o)) return false; - - WidgetsBundle that = (WidgetsBundle) o; - - if (tenantId != null ? !tenantId.equals(that.tenantId) : that.tenantId != null) return false; - if (alias != null ? !alias.equals(that.alias) : that.alias != null) return false; - if (title != null ? !title.equals(that.title) : that.title != null) return false; - if (image != null ? !image.equals(that.image) : that.image != null) return false; - if (description != null ? !description.equals(that.description) : that.description != null) return false; - return true; + public String getName() { + return title; } @Override @@ -133,7 +121,6 @@ public class WidgetsBundle extends SearchTextBased implements H sb.append("tenantId=").append(tenantId); sb.append(", alias='").append(alias).append('\''); sb.append(", title='").append(title).append('\''); - sb.append(", image='").append(image).append('\''); sb.append(", description='").append(description).append('\''); sb.append('}'); return sb.toString(); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 2bc6a18d6e..4b132d8c76 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -33,6 +33,7 @@ service EdgeRpcService { enum EdgeVersion { V_3_3_0 = 0; V_3_3_3 = 1; + V_3_4_0 = 2; } /** @@ -421,6 +422,55 @@ enum EdgeEntityType { ASSET = 1; } +message OtaPackageUpdateMsg { + UpdateMsgType msgType = 1; + int64 idMSB = 2; + int64 idLSB = 3; + int64 deviceProfileIdMSB = 4; + int64 deviceProfileIdLSB = 5; + string type = 6; + string title = 7; + string version = 8; + string tag = 9; + optional string url = 10; + optional string fileName = 11; + optional string contentType = 12; + optional string checksumAlgorithm = 13; + optional string checksum = 14; + optional int64 dataSize = 15; + optional bytes data = 16; + optional string additionalInfo = 17; +} + +message QueueUpdateMsg { + UpdateMsgType msgType = 1; + int64 idMSB = 2; + int64 idLSB = 3; + int64 tenantIdMSB = 4; + int64 tenantIdLSB = 5; + string name = 6; + string topic = 7; + int32 pollInterval = 8; + int32 partitions = 9; + bool consumerPerPartition = 10; + int64 packProcessingTimeout = 11; + SubmitStrategyProto submitStrategy = 12; + ProcessingStrategyProto processingStrategy = 13; +} + +message SubmitStrategyProto { + string type = 1; + int32 batchSize = 2; +} + +message ProcessingStrategyProto { + string type = 1; + int32 retries = 2; + double failurePercentage = 3; + int64 pauseBetweenRetries = 4; + int64 maxPauseBetweenRetries = 5; +} + /** * Main Messages; */ @@ -477,5 +527,7 @@ message DownlinkMsg { repeated WidgetTypeUpdateMsg widgetTypeUpdateMsg = 19; repeated AdminSettingsUpdateMsg adminSettingsUpdateMsg = 20; repeated DeviceRpcCallMsg deviceRpcCallMsg = 21; + repeated OtaPackageUpdateMsg otaPackageUpdateMsg = 22; + repeated QueueUpdateMsg queueUpdateMsg = 23; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index bb3be80629..f8eb8c663e 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -43,7 +43,7 @@ import java.util.UUID; @Slf4j public final class TbMsg implements Serializable { - private final QueueId queueId; + private final String queueName; private final UUID id; private final long ts; private final String type; @@ -67,12 +67,12 @@ public final class TbMsg implements Serializable { return ctx.getAndIncrementRuleNodeCounter(); } - public static TbMsg newMsg(QueueId queueId, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return newMsg(queueId, type, originator, null, metaData, data, ruleChainId, ruleNodeId); + public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId); } - public static TbMsg newMsg(QueueId queueId, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(queueId, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, + public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, null, TbMsgCallback.EMPTY); } @@ -87,12 +87,12 @@ public final class TbMsg implements Serializable { // REALLY NEW MSG - public static TbMsg newMsg(QueueId queueId, String type, EntityId originator, TbMsgMetaData metaData, String data) { - return newMsg(queueId, type, originator, null, metaData, data); + public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) { + return newMsg(queueName, type, originator, null, metaData, data); } - public static TbMsg newMsg(QueueId queueId, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { - return new TbMsg(queueId, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, + public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, null, TbMsgCallback.EMPTY); } @@ -118,40 +118,40 @@ public final class TbMsg implements Serializable { } public static TbMsg transformMsg(TbMsg tbMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { - return new TbMsg(tbMsg.queueId, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, type, originator, tbMsg.customerId, metaData.copy(), tbMsg.dataType, data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.callback); } public static TbMsg transformMsg(TbMsg tbMsg, CustomerId customerId) { - return new TbMsg(tbMsg.queueId, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.ruleChainId, tbMsg.ruleNodeId, tbMsg.ctx.copy(), tbMsg.getCallback()); } public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId) { - return new TbMsg(tbMsg.queueId, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, + return new TbMsg(tbMsg.queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, QueueId queueId) { - return new TbMsg(queueId, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, + public static TbMsg transformMsg(TbMsg tbMsg, String queueName) { + return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, tbMsg.getRuleChainId(), null, tbMsg.ctx.copy(), tbMsg.getCallback()); } - public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId, QueueId queueId) { - return new TbMsg(queueId, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, + public static TbMsg transformMsg(TbMsg tbMsg, RuleChainId ruleChainId, String queueName) { + return new TbMsg(queueName, tbMsg.id, tbMsg.ts, tbMsg.type, tbMsg.originator, tbMsg.customerId, tbMsg.metaData, tbMsg.dataType, tbMsg.data, ruleChainId, null, tbMsg.ctx.copy(), tbMsg.getCallback()); } //used for enqueueForTellNext - public static TbMsg newMsg(TbMsg tbMsg, QueueId queueId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(queueId, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(), + public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { + return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(), tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ctx.copy(), TbMsgCallback.EMPTY); } - private TbMsg(QueueId queueId, UUID id, long ts, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, + private TbMsg(String queueName, UUID id, long ts, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgProcessingCtx ctx, TbMsgCallback callback) { this.id = id; - this.queueId = queueId; + this.queueName = queueName; if (ts > 0) { this.ts = ts; } else { @@ -220,7 +220,7 @@ public final class TbMsg implements Serializable { return builder.build().toByteArray(); } - public static TbMsg fromBytes(QueueId queueId, byte[] data, TbMsgCallback callback) { + public static TbMsg fromBytes(String queueName, byte[] data, TbMsgCallback callback) { try { MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(data); TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap()); @@ -247,7 +247,7 @@ public final class TbMsg implements Serializable { } TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; - return new TbMsg(queueId, UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, customerId, + return new TbMsg(queueName, UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, customerId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, ctx, callback); } catch (InvalidProtocolBufferException e) { throw new IllegalStateException("Could not parse protobuf for TbMsg", e); @@ -259,12 +259,12 @@ public final class TbMsg implements Serializable { } public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) { - return new TbMsg(this.queueId, msgId, this.ts, this.type, this.originator, this.customerId, + return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, null, this.ctx, callback); } public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) { - return new TbMsg(this.queueId, msgId, this.ts, this.type, this.originator, this.customerId, + return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.customerId, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ctx, callback); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleListener.java b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleListener.java new file mode 100644 index 0000000000..2bcba593e4 --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/plugin/ComponentLifecycleListener.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg.plugin; + +public interface ComponentLifecycleListener { + void onComponentLifecycleMsg(ComponentLifecycleMsg componentLifecycleMsg); +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/ServiceType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/ServiceType.java index 1c05ac7f66..7f276e7e3f 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/ServiceType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/ServiceType.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.msg.queue; public enum ServiceType { - TB_CORE, TB_RULE_ENGINE, TB_TRANSPORT, JS_EXECUTOR; + TB_CORE, TB_RULE_ENGINE, TB_TRANSPORT, JS_EXECUTOR, TB_VC_EXECUTOR; public static ServiceType of(String serviceType) { return ServiceType.valueOf(serviceType.replace("-", "_").toUpperCase()); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java index 90df7fb266..c38ed85dfd 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/TbRateLimits.java @@ -20,6 +20,7 @@ import io.github.bucket4j.Bucket4j; import io.github.bucket4j.Refill; import io.github.bucket4j.local.LocalBucket; import io.github.bucket4j.local.LocalBucketBuilder; +import lombok.Getter; import java.time.Duration; @@ -28,7 +29,9 @@ import java.time.Duration; */ public class TbRateLimits { private final LocalBucket bucket; - private final String config; + + @Getter + private final String configuration; public TbRateLimits(String limitsConfiguration) { this(limitsConfiguration, false); @@ -49,7 +52,7 @@ public class TbRateLimits { } else { throw new IllegalArgumentException("Failed to parse rate limits configuration: " + limitsConfiguration); } - this.config = limitsConfiguration; + this.configuration = limitsConfiguration; } public boolean tryConsume() { @@ -60,8 +63,4 @@ public class TbRateLimits { return bucket.tryConsume(number); } - public String getConfig() { - return config; - } - } diff --git a/common/message/src/test/java/org/thingsboard/server/common/msg/tools/RateLimitsTest.java b/common/message/src/test/java/org/thingsboard/server/common/msg/tools/RateLimitsTest.java index c8583527f1..cf81690826 100644 --- a/common/message/src/test/java/org/thingsboard/server/common/msg/tools/RateLimitsTest.java +++ b/common/message/src/test/java/org/thingsboard/server/common/msg/tools/RateLimitsTest.java @@ -15,8 +15,11 @@ */ package org.thingsboard.server.common.msg.tools; +import org.awaitility.pollinterval.FixedPollInterval; +import org.awaitility.pollinterval.PollInterval; import org.junit.Test; +import java.time.Duration; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; @@ -39,10 +42,11 @@ public class RateLimitsTest { assertThat(rateLimits.tryConsume()).as("new token is available").isFalse(); int expectedRefillTime = (int) (((double) period / capacity) * 1000); - int gap = 100; + int gap = 500; for (int i = 0; i < capacity; i++) { await("token refill for rate limit " + rateLimitConfig) + .pollInterval(new FixedPollInterval(10, TimeUnit.MILLISECONDS)) .atLeast(expectedRefillTime - gap, TimeUnit.MILLISECONDS) .atMost(expectedRefillTime + gap, TimeUnit.MILLISECONDS) .untilAsserted(() -> { @@ -70,6 +74,7 @@ public class RateLimitsTest { int gap = 500; await("tokens refill for rate limit " + rateLimitConfig) + .pollInterval(new FixedPollInterval(10, TimeUnit.MILLISECONDS)) .atLeast(expectedRefillTime - gap, TimeUnit.MILLISECONDS) .atMost(expectedRefillTime + gap, TimeUnit.MILLISECONDS) .untilAsserted(() -> { diff --git a/common/pom.xml b/common/pom.xml index 236fb50a19..62bb46e81c 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -46,6 +46,7 @@ cache coap-server edge-api + version-control diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java index 9fa91a4e40..c2974c3e3a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java @@ -19,6 +19,7 @@ import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import javax.annotation.PostConstruct; import java.util.HashMap; @@ -27,17 +28,18 @@ import java.util.Map; @Component @ConditionalOnExpression("'${queue.type:null}'=='service-bus'") public class TbServiceBusQueueConfigs { - @Value("${queue.service-bus.queue-properties.core}") + @Value("${queue.service-bus.queue-properties.core:}") private String coreProperties; - @Value("${queue.service-bus.queue-properties.rule-engine}") + @Value("${queue.service-bus.queue-properties.rule-engine:}") private String ruleEngineProperties; - @Value("${queue.service-bus.queue-properties.transport-api}") + @Value("${queue.service-bus.queue-properties.transport-api:}") private String transportApiProperties; - @Value("${queue.service-bus.queue-properties.notifications}") + @Value("${queue.service-bus.queue-properties.notifications:}") private String notificationsProperties; - @Value("${queue.service-bus.queue-properties.js-executor}") + @Value("${queue.service-bus.queue-properties.js-executor:}") private String jsExecutorProperties; - + @Value("${queue.service-bus.queue-properties.version-control:}") + private String vcProperties; @Getter private Map coreConfigs; @Getter @@ -48,6 +50,8 @@ public class TbServiceBusQueueConfigs { private Map notificationsConfigs; @Getter private Map jsExecutorConfigs; + @Getter + private Map vcConfigs; @PostConstruct private void init() { @@ -56,15 +60,18 @@ public class TbServiceBusQueueConfigs { transportApiConfigs = getConfigs(transportApiProperties); notificationsConfigs = getConfigs(notificationsProperties); jsExecutorConfigs = getConfigs(jsExecutorProperties); + vcConfigs = getConfigs(vcProperties); } private Map getConfigs(String properties) { Map configs = new HashMap<>(); - for (String property : properties.split(";")) { - int delimiterPosition = property.indexOf(":"); - String key = property.substring(0, delimiterPosition); - String value = property.substring(delimiterPosition + 1); - configs.put(key, value); + if (StringUtils.isNotEmpty(properties)) { + for (String property : properties.split(";")) { + int delimiterPosition = property.indexOf(":"); + String key = property.substring(0, delimiterPosition); + String value = property.substring(delimiterPosition + 1); + configs.put(key, value); + } } return configs; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueTemplate.java index 1b193b7416..5204de42e7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueTemplate.java @@ -22,7 +22,7 @@ import java.util.UUID; public class AbstractTbQueueTemplate { protected static final String REQUEST_ID_HEADER = "requestId"; protected static final String RESPONSE_TOPIC_HEADER = "responseTopic"; - protected static final String REQUEST_TIME = "requestTime"; + protected static final String EXPIRE_TS_HEADER = "expireTs"; protected byte[] uuidToBytes(UUID uuid) { ByteBuffer buf = ByteBuffer.allocate(16); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java index e38677b2ce..1ba1422162 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java @@ -56,6 +56,7 @@ public class DefaultTbQueueRequestTemplate expectedResponse = pendingRequests.remove(requestId); if (expectedResponse == null) { log.debug("[{}] Invalid or stale request, response: {}", requestId, String.valueOf(response).replace("\n", " ")); @@ -216,7 +218,7 @@ public class DefaultTbQueueRequestTemplate future = SettableFuture.create(); ResponseMetaData responseMetaData = new ResponseMetaData<>(currentClockNs + requestTimeoutNs, future, currentClockNs, requestTimeoutNs); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java index 4859b2953b..ed3c3b8cc5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueResponseTemplate.java @@ -97,8 +97,8 @@ public class DefaultTbQueueResponseTemplate { long currentTime = System.currentTimeMillis(); - long requestTime = bytesToLong(request.getHeaders().get(REQUEST_TIME)); - if (requestTime + requestTimeout >= currentTime) { + long expireTs = bytesToLong(request.getHeaders().get(EXPIRE_TS_HEADER)); + if (expireTs >= currentTime) { byte[] requestIdHeader = request.getHeaders().get(REQUEST_ID_HEADER); if (requestIdHeader == null) { log.error("[{}] Missing requestId in header", request); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index 3317e75c91..d6bfbc9c22 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -34,6 +34,7 @@ import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent; import org.thingsboard.server.queue.util.AfterStartUp; import javax.annotation.PostConstruct; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -55,6 +56,10 @@ public class HashPartitionService implements PartitionService { private String coreTopic; @Value("${queue.core.partitions:100}") private Integer corePartitions; + @Value("${queue.vc.topic:tb_version_control}") + private String vcTopic; + @Value("${queue.vc.partitions:10}") + private Integer vcPartitions; @Value("${queue.partitions.hash_function_name:murmur3_128}") private String hashFunctionName; @@ -63,8 +68,6 @@ public class HashPartitionService implements PartitionService { private final TenantRoutingInfoService tenantRoutingInfoService; private final QueueRoutingInfoService queueRoutingInfoService; - private final ConcurrentMap queuesById = new ConcurrentHashMap<>(); - private ConcurrentMap> myPartitions = new ConcurrentHashMap<>(); private final ConcurrentMap partitionTopicsMap = new ConcurrentHashMap<>(); @@ -94,6 +97,10 @@ public class HashPartitionService implements PartitionService { partitionSizesMap.put(coreKey, corePartitions); partitionTopicsMap.put(coreKey, coreTopic); + QueueKey vcKey = new QueueKey(ServiceType.TB_VC_EXECUTOR); + partitionSizesMap.put(vcKey, vcPartitions); + partitionTopicsMap.put(vcKey, vcTopic); + if (!isTransport(serviceInfoProvider.getServiceType())) { doInitRuleEnginePartitions(); } @@ -112,7 +119,6 @@ public class HashPartitionService implements PartitionService { QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queue); partitionTopicsMap.put(queueKey, queue.getQueueTopic()); partitionSizesMap.put(queueKey, queue.getPartitions()); - queuesById.put(queue.getQueueId(), queue); }); } @@ -156,8 +162,6 @@ public class HashPartitionService implements PartitionService { public void updateQueue(TransportProtos.QueueUpdateMsg queueUpdateMsg) { TenantId tenantId = new TenantId(new UUID(queueUpdateMsg.getTenantIdMSB(), queueUpdateMsg.getTenantIdLSB())); QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueUpdateMsg.getQueueName(), tenantId); - QueueRoutingInfo queue = new QueueRoutingInfo(queueUpdateMsg); - queuesById.put(queue.getQueueId(), queue); partitionTopicsMap.put(queueKey, queueUpdateMsg.getQueueTopic()); partitionSizesMap.put(queueKey, queueUpdateMsg.getPartitions()); myPartitions.remove(queueKey); @@ -167,7 +171,6 @@ public class HashPartitionService implements PartitionService { public void removeQueue(TransportProtos.QueueDeleteMsg queueDeleteMsg) { TenantId tenantId = new TenantId(new UUID(queueDeleteMsg.getTenantIdMSB(), queueDeleteMsg.getTenantIdLSB())); QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queueDeleteMsg.getQueueName(), tenantId); - queuesById.remove(new QueueId(new UUID(queueDeleteMsg.getQueueIdMSB(), queueDeleteMsg.getQueueIdLSB()))); myPartitions.remove(queueKey); partitionTopicsMap.remove(queueKey); partitionSizesMap.remove(queueKey); @@ -176,9 +179,7 @@ public class HashPartitionService implements PartitionService { } @Override - @Deprecated - public TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId, String queueName) { - log.warn("This method is deprecated and will be removed!!!"); + public TopicPartitionInfo resolve(ServiceType serviceType, String queueName, TenantId tenantId, EntityId entityId) { TenantId isolatedOrSystemTenantId = getIsolatedOrSystemTenantId(serviceType, tenantId); QueueKey queueKey = new QueueKey(serviceType, queueName, isolatedOrSystemTenantId); if (!partitionSizesMap.containsKey(queueKey)) { @@ -192,32 +193,6 @@ public class HashPartitionService implements PartitionService { return resolve(serviceType, null, tenantId, entityId); } - @Override - public TopicPartitionInfo resolve(ServiceType serviceType, QueueId queueId, TenantId tenantId, EntityId entityId) { - QueueKey queueKey; - if (queueId == null) { - queueKey = getMainQueueKey(serviceType, tenantId); - } else { - QueueRoutingInfo queueRoutingInfo = queuesById.get(queueId); - - if (queueRoutingInfo == null) { - log.debug("Queue was removed but still used in CheckPoint rule node. [{}][{}]", tenantId, entityId); - queueKey = getMainQueueKey(serviceType, tenantId); - } else if (!queueRoutingInfo.getTenantId().equals(getIsolatedOrSystemTenantId(serviceType, tenantId))) { - log.debug("Tenant profile was changed but CheckPoint rule node still uses the queue from system level. [{}][{}]", tenantId, entityId); - queueKey = getMainQueueKey(serviceType, tenantId); - } else { - queueKey = new QueueKey(serviceType, queueRoutingInfo); - } - } - - return resolve(queueKey, entityId); - } - - private QueueKey getMainQueueKey(ServiceType serviceType, TenantId tenantId) { - return new QueueKey(serviceType, getIsolatedOrSystemTenantId(serviceType, tenantId)); - } - private TopicPartitionInfo resolve(QueueKey queueKey, EntityId entityId) { int hash = hashFunction.newHasher() .putLong(entityId.getId().getMostSignificantBits()) @@ -246,7 +221,7 @@ public class HashPartitionService implements PartitionService { myPartitions = new ConcurrentHashMap<>(); partitionSizesMap.forEach((queueKey, size) -> { for (int i = 0; i < size; i++) { - ServiceInfo serviceInfo = resolveByPartitionIdx(queueServicesMap.get(queueKey), i); + ServiceInfo serviceInfo = resolveByPartitionIdx(queueServicesMap.get(queueKey), queueKey, i); if (currentService.equals(serviceInfo)) { myPartitions.computeIfAbsent(queueKey, key -> new ArrayList<>()).add(i); } @@ -390,8 +365,6 @@ public class HashPartitionService implements PartitionService { throw new RuntimeException("Tenant not found!"); } switch (serviceType) { - case TB_CORE: - return routingInfo.isIsolatedTbCore(); case TB_RULE_ENGINE: return routingInfo.isIsolatedTbRuleEngine(); default: @@ -416,7 +389,7 @@ public class HashPartitionService implements PartitionService { queueServiceList.computeIfAbsent(key, k -> new ArrayList<>()).add(instance); } }); - } else if (ServiceType.TB_CORE.equals(serviceType)) { + } else if (ServiceType.TB_CORE.equals(serviceType) || ServiceType.TB_VC_EXECUTOR.equals(serviceType)) { queueServiceList.computeIfAbsent(new QueueKey(serviceType), key -> new ArrayList<>()).add(instance); } } @@ -426,11 +399,21 @@ public class HashPartitionService implements PartitionService { } } - private ServiceInfo resolveByPartitionIdx(List servers, Integer partitionIdx) { + protected ServiceInfo resolveByPartitionIdx(List servers, QueueKey queueKey, int partition) { if (servers == null || servers.isEmpty()) { return null; } - return servers.get(partitionIdx % servers.size()); + + if (!ServiceType.TB_RULE_ENGINE.equals(queueKey.getType()) || TenantId.SYS_TENANT_ID.equals(queueKey.getTenantId())) { + return servers.get(partition % servers.size()); + } else { + int hash = hashFunction.newHasher().putLong(queueKey.getTenantId().getId().getMostSignificantBits()) + .putLong(queueKey.getTenantId().getId().getLeastSignificantBits()) + .putString(queueKey.getQueueName(), StandardCharsets.UTF_8) + .hash().asInt(); + + return servers.get(Math.abs((hash + partition) % servers.size())); + } } public static HashFunction forName(String name) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java index 3844bf02c9..f65a83b287 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java @@ -32,13 +32,10 @@ import java.util.UUID; */ public interface PartitionService { - @Deprecated - TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId, String queueName); + TopicPartitionInfo resolve(ServiceType serviceType, String queueName, TenantId tenantId, EntityId entityId); TopicPartitionInfo resolve(ServiceType serviceType, TenantId tenantId, EntityId entityId); - TopicPartitionInfo resolve(ServiceType serviceType, QueueId queueId, TenantId tenantId, EntityId entityId); - /** * Received from the Discovery service when network topology is changed. * @param currentService - current service information {@link org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java index a2d5982421..e43da75869 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/QueueKey.java @@ -53,4 +53,11 @@ public class QueueKey { this.queueName = MAIN; this.tenantId = TenantId.SYS_TENANT_ID; } + + @Override + public String toString() { + return "QK(" + queueName + "," + type + "," + + (TenantId.SYS_TENANT_ID.equals(tenantId) ? "system" : tenantId) + + ')'; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java index 31bad5477e..7b5c142c23 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java @@ -40,7 +40,7 @@ public abstract class TbApplicationEventListener i } finally { seqNumberLock.unlock(); } - if (validUpdate) { + if (validUpdate && filterTbApplicationEvent(event)) { onTbApplicationEvent(event); } else { log.info("Application event ignored due to invalid sequence number ({} > {}). Event: {}", lastProcessedSequenceNumber, event.getSequenceNumber(), event); @@ -49,5 +49,8 @@ public abstract class TbApplicationEventListener i protected abstract void onTbApplicationEvent(T event); + protected boolean filterTbApplicationEvent(T event) { + return true; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfo.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfo.java index 01c1288635..165bf9459a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfo.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TenantRoutingInfo.java @@ -21,6 +21,5 @@ import org.thingsboard.server.common.data.id.TenantId; @Data public class TenantRoutingInfo { private final TenantId tenantId; - private final boolean isolatedTbCore; private final boolean isolatedTbRuleEngine; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 89eda16cad..5976a1372b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -33,7 +33,6 @@ import org.apache.zookeeper.KeeperException; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java index d5a9a34dc7..530a7a1a58 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java @@ -51,7 +51,7 @@ public class TbKafkaAdmin implements TbQueueAdmin { log.error("Failed to get all topics.", e); } - String numPartitionsStr = topicConfigs.get("partitions"); + String numPartitionsStr = topicConfigs.get(TbKafkaTopicConfigs.NUM_PARTITIONS_SETTING); if (numPartitionsStr != null) { numPartitions = Integer.parseInt(numPartitionsStr); topicConfigs.remove("partitions"); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java index 4c9194b675..c02a1af9d5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java @@ -28,40 +28,55 @@ import java.util.Map; @Component @ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka") public class TbKafkaTopicConfigs { - @Value("${queue.kafka.topic-properties.core}") + public static final String NUM_PARTITIONS_SETTING = "partitions"; + + @Value("${queue.kafka.topic-properties.core:}") private String coreProperties; - @Value("${queue.kafka.topic-properties.rule-engine}") + @Value("${queue.kafka.topic-properties.rule-engine:}") private String ruleEngineProperties; - @Value("${queue.kafka.topic-properties.transport-api}") + @Value("${queue.kafka.topic-properties.transport-api:}") private String transportApiProperties; - @Value("${queue.kafka.topic-properties.notifications}") + @Value("${queue.kafka.topic-properties.notifications:}") private String notificationsProperties; - @Value("${queue.kafka.topic-properties.js-executor}") + @Value("${queue.kafka.topic-properties.js-executor:}") private String jsExecutorProperties; @Value("${queue.kafka.topic-properties.ota-updates:}") private String fwUpdatesProperties; + @Value("${queue.kafka.topic-properties.version-control:}") + private String vcProperties; @Getter private Map coreConfigs; @Getter private Map ruleEngineConfigs; @Getter - private Map transportApiConfigs; + private Map transportApiRequestConfigs; + @Getter + private Map transportApiResponseConfigs; @Getter private Map notificationsConfigs; @Getter - private Map jsExecutorConfigs; + private Map jsExecutorRequestConfigs; + @Getter + private Map jsExecutorResponseConfigs; @Getter private Map fwUpdatesConfigs; + @Getter + private Map vcConfigs; @PostConstruct private void init() { coreConfigs = getConfigs(coreProperties); ruleEngineConfigs = getConfigs(ruleEngineProperties); - transportApiConfigs = getConfigs(transportApiProperties); + transportApiRequestConfigs = getConfigs(transportApiProperties); + transportApiResponseConfigs = getConfigs(transportApiProperties); + transportApiResponseConfigs.put(NUM_PARTITIONS_SETTING, "1"); notificationsConfigs = getConfigs(notificationsProperties); - jsExecutorConfigs = getConfigs(jsExecutorProperties); + jsExecutorRequestConfigs = getConfigs(jsExecutorProperties); + jsExecutorResponseConfigs = getConfigs(jsExecutorProperties); + jsExecutorResponseConfigs.put(NUM_PARTITIONS_SETTING, "1"); fwUpdatesConfigs = getConfigs(fwUpdatesProperties); + vcConfigs = getConfigs(vcProperties); } private Map getConfigs(String properties) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java index 660173c71d..41efe08c1c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -46,6 +47,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin; import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; @@ -57,7 +59,7 @@ import java.nio.charset.StandardCharsets; @Component @ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && '${service.type:null}'=='monolith'") -public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory { +public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory { private final NotificationsTopicService notificationsTopicService; private final TbQueueCoreSettings coreSettings; @@ -66,6 +68,7 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbAwsSqsSettings sqsSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; private final TbQueueAdmin coreAdmin; @@ -73,6 +76,8 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin transportApiAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin otaAdmin; + private final TbQueueAdmin vcAdmin; public AwsSqsMonolithQueueFactory(NotificationsTopicService notificationsTopicService, TbQueueCoreSettings coreSettings, TbQueueRuleEngineSettings ruleEngineSettings, @@ -80,6 +85,7 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng TbQueueTransportApiSettings transportApiSettings, TbQueueTransportNotificationSettings transportNotificationSettings, TbAwsSqsSettings sqsSettings, + TbQueueVersionControlSettings vcSettings, TbAwsSqsQueueAttributes sqsQueueAttributes, TbQueueRemoteJsInvokeSettings jsInvokeSettings) { this.notificationsTopicService = notificationsTopicService; @@ -89,6 +95,7 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng this.transportApiSettings = transportApiSettings; this.transportNotificationSettings = transportNotificationSettings; this.sqsSettings = sqsSettings; + this.vcSettings = vcSettings; this.jsInvokeSettings = jsInvokeSettings; this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes()); @@ -96,6 +103,8 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng this.jsExecutorAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getJsExecutorAttributes()); this.transportApiAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getTransportApiAttributes()); this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes()); + this.otaAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getOtaAttributes()); + this.vcAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getVcAttributes()); } @Override @@ -123,6 +132,13 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, coreSettings.getTopic()); } + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(vcAdmin, sqsSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { return new TbAwsSqsConsumerTemplate<>(ruleEngineAdmin, sqsSettings, configuration.getTopic(), @@ -196,13 +212,18 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng @Override public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { - return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic(), + return new TbAwsSqsConsumerTemplate<>(otaAdmin, sqsSettings, coreSettings.getOtaPackageTopic(), msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { - return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); + return new TbAwsSqsProducerTemplate<>(otaAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); + } + + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + return new TbAwsSqsProducerTemplate<>(vcAdmin, sqsSettings, vcSettings.getTopic()); } @PreDestroy @@ -222,5 +243,11 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng if (notificationAdmin != null) { notificationAdmin.destroy(); } + if (otaAdmin != null) { + otaAdmin.destroy(); + } + if (vcAdmin != null) { + vcAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java index 81736c2b55..17dcd07919 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -44,6 +45,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin; import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; @@ -65,18 +67,22 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { private final TbServiceInfoProvider serviceInfoProvider; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; private final TbQueueTransportNotificationSettings transportNotificationSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin transportApiAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin otaAdmin; + private final TbQueueAdmin vcAdmin; public AwsSqsTbCoreQueueFactory(TbAwsSqsSettings sqsSettings, TbQueueCoreSettings coreSettings, TbQueueTransportApiSettings transportApiSettings, TbQueueRuleEngineSettings ruleEngineSettings, NotificationsTopicService notificationsTopicService, + TbQueueVersionControlSettings vcSettings, TbServiceInfoProvider serviceInfoProvider, TbQueueRemoteJsInvokeSettings jsInvokeSettings, TbAwsSqsQueueAttributes sqsQueueAttributes, @@ -89,12 +95,15 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { this.serviceInfoProvider = serviceInfoProvider; this.jsInvokeSettings = jsInvokeSettings; this.transportNotificationSettings = transportNotificationSettings; + this.vcSettings = vcSettings; this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes()); this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes()); this.jsExecutorAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getJsExecutorAttributes()); this.transportApiAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getTransportApiAttributes()); this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes()); + this.otaAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getOtaAttributes()); + this.vcAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getVcAttributes()); } @Override @@ -182,13 +191,18 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { @Override public TbQueueConsumer> createToOtaPackageStateServiceMsgConsumer() { - return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic(), + return new TbAwsSqsConsumerTemplate<>(otaAdmin, sqsSettings, coreSettings.getOtaPackageTopic(), msg -> new TbProtoQueueMsg<>(msg.getKey(), ToOtaPackageStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { - return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); + return new TbAwsSqsProducerTemplate<>(otaAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); + } + + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + return new TbAwsSqsProducerTemplate<>(vcAdmin, sqsSettings, vcSettings.getTopic()); } @PreDestroy @@ -208,5 +222,11 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { if (notificationAdmin != null) { notificationAdmin.destroy(); } + if (otaAdmin != null) { + otaAdmin.destroy(); + } + if (vcAdmin != null) { + vcAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java index 9d9fd29078..94df62eb3a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java @@ -39,6 +39,7 @@ import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin; import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate; import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; @@ -64,6 +65,7 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory private final TbQueueAdmin ruleEngineAdmin; private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin otaAdmin; public AwsSqsTbRuleEngineQueueFactory(NotificationsTopicService notificationsTopicService, TbQueueCoreSettings coreSettings, TbQueueRuleEngineSettings ruleEngineSettings, @@ -84,6 +86,7 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory this.ruleEngineAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getRuleEngineAttributes()); this.jsExecutorAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getJsExecutorAttributes()); this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes()); + this.otaAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getOtaAttributes()); } @Override @@ -154,7 +157,7 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory @Override public TbQueueProducer> createToOtaPackageStateServiceMsgProducer() { - return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); + return new TbAwsSqsProducerTemplate<>(otaAdmin, sqsSettings, coreSettings.getOtaPackageTopic()); } @@ -172,6 +175,9 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory if (notificationAdmin != null) { notificationAdmin.destroy(); } + if (otaAdmin != null) { + otaAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java new file mode 100644 index 0000000000..d76b3f595d --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbVersionControlQueueFactory.java @@ -0,0 +1,91 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.provider; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueAdmin; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.settings.TbQueueCoreSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; +import org.thingsboard.server.queue.sqs.TbAwsSqsAdmin; +import org.thingsboard.server.queue.sqs.TbAwsSqsConsumerTemplate; +import org.thingsboard.server.queue.sqs.TbAwsSqsProducerTemplate; +import org.thingsboard.server.queue.sqs.TbAwsSqsQueueAttributes; +import org.thingsboard.server.queue.sqs.TbAwsSqsSettings; + +import javax.annotation.PreDestroy; + +@Component +@ConditionalOnExpression("'${queue.type:null}'=='aws-sqs' && '${service.type:null}'=='tb-vc-executor'") +public class AwsSqsTbVersionControlQueueFactory implements TbVersionControlQueueFactory { + + private final TbAwsSqsSettings sqsSettings; + private final TbQueueCoreSettings coreSettings; + private final TbQueueVersionControlSettings vcSettings; + + + private final TbQueueAdmin coreAdmin; + private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin vcAdmin; + + public AwsSqsTbVersionControlQueueFactory(TbAwsSqsSettings sqsSettings, + TbQueueCoreSettings coreSettings, + TbQueueVersionControlSettings vcSettings, + TbAwsSqsQueueAttributes sqsQueueAttributes + ) { + this.sqsSettings = sqsSettings; + this.coreSettings = coreSettings; + this.vcSettings = vcSettings; + + this.coreAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getCoreAttributes()); + this.notificationAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getNotificationsAttributes()); + this.vcAdmin = new TbAwsSqsAdmin(sqsSettings, sqsQueueAttributes.getVcAttributes()); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, coreSettings.getTopic()); + } + + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(vcAdmin, sqsSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + + @PreDestroy + private void destroy() { + if (coreAdmin != null) { + coreAdmin.destroy(); + } + if (notificationAdmin != null) { + notificationAdmin.destroy(); + } + if (vcAdmin != null) { + vcAdmin.destroy(); + } + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java index fe87fc2886..815e0efdf8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java @@ -37,28 +37,32 @@ import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; @Slf4j @Component @ConditionalOnExpression("'${queue.type:null}'=='in-memory' && '${service.type:null}'=='monolith'") -public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory { +public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory { private final NotificationsTopicService notificationsTopicService; private final TbQueueCoreSettings coreSettings; private final TbServiceInfoProvider serviceInfoProvider; private final TbQueueRuleEngineSettings ruleEngineSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueTransportNotificationSettings transportNotificationSettings; private final InMemoryStorage storage; public InMemoryMonolithQueueFactory(NotificationsTopicService notificationsTopicService, TbQueueCoreSettings coreSettings, TbQueueRuleEngineSettings ruleEngineSettings, + TbQueueVersionControlSettings vcSettings, TbServiceInfoProvider serviceInfoProvider, TbQueueTransportApiSettings transportApiSettings, TbQueueTransportNotificationSettings transportNotificationSettings, InMemoryStorage storage) { this.notificationsTopicService = notificationsTopicService; this.coreSettings = coreSettings; + this.vcSettings = vcSettings; this.serviceInfoProvider = serviceInfoProvider; this.ruleEngineSettings = ruleEngineSettings; this.transportApiSettings = transportApiSettings; @@ -91,6 +95,11 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return new InMemoryTbQueueProducer<>(storage, coreSettings.getTopic()); } + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new InMemoryTbQueueConsumer<>(storage, vcSettings.getTopic()); + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { return new InMemoryTbQueueConsumer<>(storage, configuration.getTopic()); @@ -146,6 +155,11 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return new InMemoryTbQueueProducer<>(storage, coreSettings.getUsageStatsTopic()); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + return new InMemoryTbQueueProducer<>(storage, vcSettings.getTopic()); + } + @Scheduled(fixedRateString = "${queue.in_memory.stats.print-interval-ms:60000}") private void printInMemoryStats() { storage.printStats(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index 104c531e12..54a1a8b3a4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -51,6 +52,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import javax.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @@ -58,7 +60,7 @@ import java.util.concurrent.atomic.AtomicLong; @Component @ConditionalOnExpression("'${queue.type:null}'=='kafka' && '${service.type:null}'=='monolith'") -public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory { +public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory { private final NotificationsTopicService notificationsTopicService; private final TbKafkaSettings kafkaSettings; @@ -68,14 +70,19 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbKafkaConsumerStatsService consumerStatsService; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; - private final TbQueueAdmin jsExecutorAdmin; - private final TbQueueAdmin transportApiAdmin; + private final TbQueueAdmin jsExecutorRequestAdmin; + private final TbQueueAdmin jsExecutorResponseAdmin; + private final TbQueueAdmin transportApiRequestAdmin; + private final TbQueueAdmin transportApiResponseAdmin; private final TbQueueAdmin notificationAdmin; private final TbQueueAdmin fwUpdatesAdmin; + private final TbQueueAdmin vcAdmin; + private final AtomicLong consumerCount = new AtomicLong(); public KafkaMonolithQueueFactory(NotificationsTopicService notificationsTopicService, TbKafkaSettings kafkaSettings, @@ -85,6 +92,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi TbQueueTransportApiSettings transportApiSettings, TbQueueTransportNotificationSettings transportNotificationSettings, TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbQueueVersionControlSettings vcSettings, TbKafkaConsumerStatsService consumerStatsService, TbKafkaTopicConfigs kafkaTopicConfigs) { this.notificationsTopicService = notificationsTopicService; @@ -95,14 +103,18 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi this.transportApiSettings = transportApiSettings; this.transportNotificationSettings = transportNotificationSettings; this.jsInvokeSettings = jsInvokeSettings; + this.vcSettings = vcSettings; this.consumerStatsService = consumerStatsService; this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); - this.jsExecutorAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorConfigs()); - this.transportApiAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiConfigs()); + this.jsExecutorRequestAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorRequestConfigs()); + this.jsExecutorResponseAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorResponseConfigs()); + this.transportApiRequestAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiRequestConfigs()); + this.transportApiResponseAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiResponseConfigs()); this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs()); this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs()); + this.vcAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getVcConfigs()); } @Override @@ -155,6 +167,19 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi return requestBuilder.build(); } + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(vcSettings.getTopic()); + consumerBuilder.clientId("monolith-vc-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("monolith-vc-node"); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(vcAdmin); + consumerBuilder.statsService(consumerStatsService); + return consumerBuilder.build(); + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { String queueName = configuration.getName(); @@ -216,7 +241,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi consumerBuilder.clientId("monolith-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.groupId("monolith-transport-api-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); - consumerBuilder.admin(transportApiAdmin); + consumerBuilder.admin(transportApiRequestAdmin); consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -227,7 +252,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi requestBuilder.settings(kafkaSettings); requestBuilder.clientId("monolith-transport-api-producer-" + serviceInfoProvider.getServiceId()); requestBuilder.defaultTopic(transportApiSettings.getResponsesTopic()); - requestBuilder.admin(transportApiAdmin); + requestBuilder.admin(transportApiResponseAdmin); return requestBuilder.build(); } @@ -238,7 +263,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi requestBuilder.settings(kafkaSettings); requestBuilder.clientId("producer-js-invoke-" + serviceInfoProvider.getServiceId()); requestBuilder.defaultTopic(jsInvokeSettings.getRequestTopic()); - requestBuilder.admin(jsExecutorAdmin); + requestBuilder.admin(jsExecutorRequestAdmin); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> responseBuilder = TbKafkaConsumerTemplate.builder(); responseBuilder.settings(kafkaSettings); @@ -252,11 +277,11 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi } ); responseBuilder.statsService(consumerStatsService); - responseBuilder.admin(jsExecutorAdmin); + responseBuilder.admin(jsExecutorResponseAdmin); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> builder = DefaultTbQueueRequestTemplate.builder(); - builder.queueAdmin(jsExecutorAdmin); + builder.queueAdmin(jsExecutorResponseAdmin); builder.requestTemplate(requestBuilder.build()); builder.responseTemplate(responseBuilder.build()); builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests()); @@ -311,6 +336,16 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi return requestBuilder.build(); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("monolith-vc-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(vcSettings.getTopic()); + requestBuilder.admin(vcAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { @@ -319,11 +354,17 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi if (ruleEngineAdmin != null) { ruleEngineAdmin.destroy(); } - if (jsExecutorAdmin != null) { - jsExecutorAdmin.destroy(); + if (jsExecutorRequestAdmin != null) { + jsExecutorRequestAdmin.destroy(); } - if (transportApiAdmin != null) { - transportApiAdmin.destroy(); + if (jsExecutorResponseAdmin != null) { + jsExecutorResponseAdmin.destroy(); + } + if (transportApiRequestAdmin != null) { + transportApiRequestAdmin.destroy(); + } + if (transportApiResponseAdmin != null) { + transportApiResponseAdmin.destroy(); } if (notificationAdmin != null) { notificationAdmin.destroy(); @@ -331,5 +372,8 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi if (fwUpdatesAdmin != null) { fwUpdatesAdmin.destroy(); } + if (vcAdmin != null) { + vcAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index 58dec0792a..6a7c6380fa 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -50,6 +51,7 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import javax.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @@ -65,15 +67,19 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { private final TbQueueRuleEngineSettings ruleEngineSettings; private final TbQueueTransportApiSettings transportApiSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbKafkaConsumerStatsService consumerStatsService; private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; - private final TbQueueAdmin jsExecutorAdmin; - private final TbQueueAdmin transportApiAdmin; + private final TbQueueAdmin jsExecutorRequestAdmin; + private final TbQueueAdmin jsExecutorResponseAdmin; + private final TbQueueAdmin transportApiRequestAdmin; + private final TbQueueAdmin transportApiResponseAdmin; private final TbQueueAdmin notificationAdmin; private final TbQueueAdmin fwUpdatesAdmin; + private final TbQueueAdmin vcAdmin; public KafkaTbCoreQueueFactory(NotificationsTopicService notificationsTopicService, TbKafkaSettings kafkaSettings, @@ -82,6 +88,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { TbQueueRuleEngineSettings ruleEngineSettings, TbQueueTransportApiSettings transportApiSettings, TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbQueueVersionControlSettings vcSettings, TbKafkaConsumerStatsService consumerStatsService, TbQueueTransportNotificationSettings transportNotificationSettings, TbKafkaTopicConfigs kafkaTopicConfigs) { @@ -92,15 +99,19 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { this.ruleEngineSettings = ruleEngineSettings; this.transportApiSettings = transportApiSettings; this.jsInvokeSettings = jsInvokeSettings; + this.vcSettings = vcSettings; this.consumerStatsService = consumerStatsService; this.transportNotificationSettings = transportNotificationSettings; this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); - this.jsExecutorAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorConfigs()); - this.transportApiAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiConfigs()); + this.jsExecutorRequestAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorRequestConfigs()); + this.jsExecutorResponseAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorResponseConfigs()); + this.transportApiRequestAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiRequestConfigs()); + this.transportApiResponseAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiResponseConfigs()); this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs()); this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs()); + this.vcAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getVcConfigs()); } @Override @@ -187,7 +198,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { consumerBuilder.clientId("tb-core-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.groupId("tb-core-transport-api-consumer"); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); - consumerBuilder.admin(transportApiAdmin); + consumerBuilder.admin(transportApiRequestAdmin); consumerBuilder.statsService(consumerStatsService); return consumerBuilder.build(); } @@ -198,7 +209,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { requestBuilder.settings(kafkaSettings); requestBuilder.clientId("tb-core-transport-api-producer-" + serviceInfoProvider.getServiceId()); requestBuilder.defaultTopic(transportApiSettings.getResponsesTopic()); - requestBuilder.admin(transportApiAdmin); + requestBuilder.admin(transportApiResponseAdmin); return requestBuilder.build(); } @@ -209,7 +220,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { requestBuilder.settings(kafkaSettings); requestBuilder.clientId("producer-js-invoke-" + serviceInfoProvider.getServiceId()); requestBuilder.defaultTopic(jsInvokeSettings.getRequestTopic()); - requestBuilder.admin(jsExecutorAdmin); + requestBuilder.admin(jsExecutorRequestAdmin); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> responseBuilder = TbKafkaConsumerTemplate.builder(); responseBuilder.settings(kafkaSettings); @@ -222,12 +233,12 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders()); } ); - responseBuilder.admin(jsExecutorAdmin); + responseBuilder.admin(jsExecutorResponseAdmin); responseBuilder.statsService(consumerStatsService); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> builder = DefaultTbQueueRequestTemplate.builder(); - builder.queueAdmin(jsExecutorAdmin); + builder.queueAdmin(jsExecutorResponseAdmin); builder.requestTemplate(requestBuilder.build()); builder.responseTemplate(responseBuilder.build()); builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests()); @@ -282,6 +293,16 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { return requestBuilder.build(); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-core-vc-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(vcSettings.getTopic()); + requestBuilder.admin(vcAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { @@ -290,11 +311,17 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { if (ruleEngineAdmin != null) { ruleEngineAdmin.destroy(); } - if (jsExecutorAdmin != null) { - jsExecutorAdmin.destroy(); + if (jsExecutorRequestAdmin != null) { + jsExecutorRequestAdmin.destroy(); + } + if (jsExecutorResponseAdmin != null) { + jsExecutorResponseAdmin.destroy(); } - if (transportApiAdmin != null) { - transportApiAdmin.destroy(); + if (transportApiRequestAdmin != null) { + transportApiRequestAdmin.destroy(); + } + if (transportApiResponseAdmin != null) { + transportApiResponseAdmin.destroy(); } if (notificationAdmin != null) { notificationAdmin.destroy(); @@ -302,5 +329,8 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { if (fwUpdatesAdmin != null) { fwUpdatesAdmin.destroy(); } + if (vcAdmin != null) { + vcAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java index 12a51fc8fd..13f3361232 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java @@ -68,7 +68,8 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; - private final TbQueueAdmin jsExecutorAdmin; + private final TbQueueAdmin jsExecutorRequestAdmin; + private final TbQueueAdmin jsExecutorResponseAdmin; private final TbQueueAdmin notificationAdmin; private final TbQueueAdmin fwUpdatesAdmin; private final AtomicLong consumerCount = new AtomicLong(); @@ -92,7 +93,8 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); - this.jsExecutorAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorConfigs()); + this.jsExecutorRequestAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorRequestConfigs()); + this.jsExecutorResponseAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorResponseConfigs()); this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs()); this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs()); } @@ -191,7 +193,7 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { requestBuilder.settings(kafkaSettings); requestBuilder.clientId("producer-js-invoke-" + serviceInfoProvider.getServiceId()); requestBuilder.defaultTopic(jsInvokeSettings.getRequestTopic()); - requestBuilder.admin(jsExecutorAdmin); + requestBuilder.admin(jsExecutorRequestAdmin); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> responseBuilder = TbKafkaConsumerTemplate.builder(); responseBuilder.settings(kafkaSettings); @@ -204,12 +206,12 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { return new TbProtoQueueMsg<>(msg.getKey(), builder.build(), msg.getHeaders()); } ); - responseBuilder.admin(jsExecutorAdmin); + responseBuilder.admin(jsExecutorResponseAdmin); responseBuilder.statsService(consumerStatsService); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> builder = DefaultTbQueueRequestTemplate.builder(); - builder.queueAdmin(jsExecutorAdmin); + builder.queueAdmin(jsExecutorResponseAdmin); builder.requestTemplate(requestBuilder.build()); builder.responseTemplate(responseBuilder.build()); builder.maxPendingRequests(jsInvokeSettings.getMaxPendingRequests()); @@ -236,8 +238,11 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { if (ruleEngineAdmin != null) { ruleEngineAdmin.destroy(); } - if (jsExecutorAdmin != null) { - jsExecutorAdmin.destroy(); + if (jsExecutorRequestAdmin != null) { + jsExecutorRequestAdmin.destroy(); + } + if (jsExecutorResponseAdmin != null) { + jsExecutorResponseAdmin.destroy(); } if (notificationAdmin != null) { notificationAdmin.destroy(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java index 8e692573c7..c05120fbf2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java @@ -59,7 +59,8 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; - private final TbQueueAdmin transportApiAdmin; + private final TbQueueAdmin transportApiRequestAdmin; + private final TbQueueAdmin transportApiResponseAdmin; private final TbQueueAdmin notificationAdmin; public KafkaTbTransportQueueFactory(TbKafkaSettings kafkaSettings, @@ -80,7 +81,8 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getRuleEngineConfigs()); - this.transportApiAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiConfigs()); + this.transportApiRequestAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiRequestConfigs()); + this.transportApiResponseAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiResponseConfigs()); this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs()); } @@ -90,7 +92,7 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { requestBuilder.settings(kafkaSettings); requestBuilder.clientId("transport-api-request-" + serviceInfoProvider.getServiceId()); requestBuilder.defaultTopic(transportApiSettings.getRequestsTopic()); - requestBuilder.admin(transportApiAdmin); + requestBuilder.admin(transportApiRequestAdmin); TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> responseBuilder = TbKafkaConsumerTemplate.builder(); responseBuilder.settings(kafkaSettings); @@ -98,12 +100,12 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { responseBuilder.clientId("transport-api-response-" + serviceInfoProvider.getServiceId()); responseBuilder.groupId("transport-node-" + serviceInfoProvider.getServiceId()); responseBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders())); - responseBuilder.admin(transportApiAdmin); + responseBuilder.admin(transportApiResponseAdmin); responseBuilder.statsService(consumerStatsService); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder , TbProtoQueueMsg> templateBuilder = DefaultTbQueueRequestTemplate.builder(); - templateBuilder.queueAdmin(transportApiAdmin); + templateBuilder.queueAdmin(transportApiResponseAdmin); templateBuilder.requestTemplate(requestBuilder.build()); templateBuilder.responseTemplate(responseBuilder.build()); templateBuilder.maxPendingRequests(transportApiSettings.getMaxPendingRequests()); @@ -163,8 +165,11 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { if (ruleEngineAdmin != null) { ruleEngineAdmin.destroy(); } - if (transportApiAdmin != null) { - transportApiAdmin.destroy(); + if (transportApiRequestAdmin != null) { + transportApiRequestAdmin.destroy(); + } + if (transportApiResponseAdmin != null) { + transportApiResponseAdmin.destroy(); } if (notificationAdmin != null) { notificationAdmin.destroy(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbVersionControlQueueFactory.java new file mode 100644 index 0000000000..598f86d0f4 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbVersionControlQueueFactory.java @@ -0,0 +1,116 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.provider; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; +import org.thingsboard.server.queue.TbQueueAdmin; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.kafka.TbKafkaAdmin; +import org.thingsboard.server.queue.kafka.TbKafkaConsumerStatsService; +import org.thingsboard.server.queue.kafka.TbKafkaConsumerTemplate; +import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate; +import org.thingsboard.server.queue.kafka.TbKafkaSettings; +import org.thingsboard.server.queue.kafka.TbKafkaTopicConfigs; +import org.thingsboard.server.queue.settings.TbQueueCoreSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; + +import javax.annotation.PreDestroy; + +@Component +@ConditionalOnExpression("'${queue.type:null}'=='kafka' && '${service.type:null}'=='tb-vc-executor'") +public class KafkaTbVersionControlQueueFactory implements TbVersionControlQueueFactory { + + private final TbKafkaSettings kafkaSettings; + private final TbServiceInfoProvider serviceInfoProvider; + private final TbQueueCoreSettings coreSettings; + private final TbQueueVersionControlSettings vcSettings; + private final TbKafkaConsumerStatsService consumerStatsService; + + private final TbQueueAdmin coreAdmin; + private final TbQueueAdmin vcAdmin; + private final TbQueueAdmin notificationAdmin; + + public KafkaTbVersionControlQueueFactory(TbKafkaSettings kafkaSettings, + TbServiceInfoProvider serviceInfoProvider, + TbQueueCoreSettings coreSettings, + TbQueueVersionControlSettings vcSettings, + TbKafkaConsumerStatsService consumerStatsService, + TbKafkaTopicConfigs kafkaTopicConfigs) { + this.kafkaSettings = kafkaSettings; + this.serviceInfoProvider = serviceInfoProvider; + this.coreSettings = coreSettings; + this.vcSettings = vcSettings; + this.consumerStatsService = consumerStatsService; + + this.coreAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getCoreConfigs()); + this.vcAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getVcConfigs()); + this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs()); + } + + + @Override + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-vc-to-core-notifications-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getTopic()); + requestBuilder.admin(notificationAdmin); + return requestBuilder.build(); + } + + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(vcSettings.getTopic()); + consumerBuilder.clientId("tb-vc-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("tb-vc-node"); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(vcAdmin); + consumerBuilder.statsService(consumerStatsService); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-vc-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + + @PreDestroy + private void destroy() { + if (coreAdmin != null) { + coreAdmin.destroy(); + } + if (vcAdmin != null) { + vcAdmin.destroy(); + } + if (notificationAdmin != null) { + notificationAdmin.destroy(); + } + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java index aceb9d4f0d..d439bb0ed7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -51,13 +52,14 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import javax.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component @ConditionalOnExpression("'${queue.type:null}'=='pubsub' && '${service.type:null}'=='monolith'") -public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory { +public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory { private final TbPubSubSettings pubSubSettings; private final TbQueueCoreSettings coreSettings; @@ -67,12 +69,14 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng private final NotificationsTopicService notificationsTopicService; private final TbServiceInfoProvider serviceInfoProvider; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin transportApiAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin vcAdmin; public PubSubMonolithQueueFactory(TbPubSubSettings pubSubSettings, TbQueueCoreSettings coreSettings, @@ -82,7 +86,8 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng NotificationsTopicService notificationsTopicService, TbServiceInfoProvider serviceInfoProvider, TbPubSubSubscriptionSettings pubSubSubscriptionSettings, - TbQueueRemoteJsInvokeSettings jsInvokeSettings) { + TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbQueueVersionControlSettings vcSettings) { this.pubSubSettings = pubSubSettings; this.coreSettings = coreSettings; this.ruleEngineSettings = ruleEngineSettings; @@ -90,12 +95,15 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng this.transportNotificationSettings = transportNotificationSettings; this.notificationsTopicService = notificationsTopicService; this.serviceInfoProvider = serviceInfoProvider; + this.vcSettings = vcSettings; this.coreAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getCoreSettings()); this.ruleEngineAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getRuleEngineSettings()); this.jsExecutorAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getJsExecutorSettings()); this.transportApiAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getTransportApiSettings()); this.notificationAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getNotificationsSettings()); + this.vcAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getVcSettings()); + this.jsInvokeSettings = jsInvokeSettings; } @@ -125,6 +133,13 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, coreSettings.getTopic()); } + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbPubSubConsumerTemplate<>(vcAdmin, pubSubSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { return new TbPubSubConsumerTemplate<>(ruleEngineAdmin, pubSubSettings, configuration.getTopic(), @@ -207,6 +222,11 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + return new TbPubSubProducerTemplate<>(vcAdmin, pubSubSettings, vcSettings.getTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { @@ -224,5 +244,8 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng if (notificationAdmin != null) { notificationAdmin.destroy(); } + if (vcAdmin != null) { + vcAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java index 18a6668581..6cf9aa9a45 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -191,6 +192,12 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory { return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + //TODO: version-control + return null; + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbVersionControlQueueFactory.java new file mode 100644 index 0000000000..45e71b2299 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbVersionControlQueueFactory.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.queue.provider; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueAdmin; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.pubsub.TbPubSubAdmin; +import org.thingsboard.server.queue.pubsub.TbPubSubConsumerTemplate; +import org.thingsboard.server.queue.pubsub.TbPubSubProducerTemplate; +import org.thingsboard.server.queue.pubsub.TbPubSubSettings; +import org.thingsboard.server.queue.pubsub.TbPubSubSubscriptionSettings; +import org.thingsboard.server.queue.settings.TbQueueCoreSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; + +import javax.annotation.PreDestroy; + +@Component +@ConditionalOnExpression("'${queue.type:null}'=='pubsub' && '${service.type:null}'=='tb-vc-executor'") +public class PubSubTbVersionControlQueueFactory implements TbVersionControlQueueFactory { + + private final TbPubSubSettings pubSubSettings; + private final TbQueueCoreSettings coreSettings; + private final TbQueueVersionControlSettings vcSettings; + + private final TbQueueAdmin coreAdmin; + private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin vcAdmin; + + public PubSubTbVersionControlQueueFactory(TbPubSubSettings pubSubSettings, + TbQueueCoreSettings coreSettings, + TbQueueVersionControlSettings vcSettings, + TbPubSubSubscriptionSettings pubSubSubscriptionSettings + ) { + this.pubSubSettings = pubSubSettings; + this.coreSettings = coreSettings; + this.vcSettings = vcSettings; + + this.coreAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getCoreSettings()); + this.notificationAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getNotificationsSettings()); + this.vcAdmin = new TbPubSubAdmin(pubSubSettings, pubSubSubscriptionSettings.getVcSettings()); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + return new TbPubSubProducerTemplate<>(notificationAdmin, pubSubSettings, coreSettings.getTopic()); + } + + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbPubSubConsumerTemplate<>(vcAdmin, pubSubSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + + @PreDestroy + private void destroy() { + if (coreAdmin != null) { + coreAdmin.destroy(); + } + if (notificationAdmin != null) { + notificationAdmin.destroy(); + } + if (vcAdmin != null) { + vcAdmin.destroy(); + } + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java index 2a724289bb..3223e46cc5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -51,13 +52,14 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import javax.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component @ConditionalOnExpression("'${queue.type:null}'=='rabbitmq' && '${service.type:null}'=='monolith'") -public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory { +public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory { private final NotificationsTopicService notificationsTopicService; private final TbQueueCoreSettings coreSettings; @@ -67,12 +69,14 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbRabbitMqSettings rabbitMqSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin transportApiAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin vcAdmin; public RabbitMqMonolithQueueFactory(NotificationsTopicService notificationsTopicService, TbQueueCoreSettings coreSettings, TbQueueRuleEngineSettings ruleEngineSettings, @@ -81,7 +85,8 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE TbQueueTransportNotificationSettings transportNotificationSettings, TbRabbitMqSettings rabbitMqSettings, TbRabbitMqQueueArguments queueArguments, - TbQueueRemoteJsInvokeSettings jsInvokeSettings) { + TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbQueueVersionControlSettings vcSettings) { this.notificationsTopicService = notificationsTopicService; this.coreSettings = coreSettings; this.serviceInfoProvider = serviceInfoProvider; @@ -90,12 +95,14 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE this.transportNotificationSettings = transportNotificationSettings; this.rabbitMqSettings = rabbitMqSettings; this.jsInvokeSettings = jsInvokeSettings; + this.vcSettings = vcSettings; this.coreAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getCoreArgs()); this.ruleEngineAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getRuleEngineArgs()); this.jsExecutorAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getJsExecutorArgs()); this.transportApiAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getTransportApiArgs()); this.notificationAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getNotificationsArgs()); + this.vcAdmin = new TbRabbitMqAdmin(rabbitMqSettings, queueArguments.getVcArgs()); } @Override @@ -123,6 +130,13 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, coreSettings.getTopic()); } + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(vcAdmin, rabbitMqSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { return new TbRabbitMqConsumerTemplate<>(ruleEngineAdmin, rabbitMqSettings, configuration.getTopic(), @@ -205,6 +219,11 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + return new TbRabbitMqProducerTemplate<>(vcAdmin, rabbitMqSettings, vcSettings.getTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { @@ -222,5 +241,8 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE if (notificationAdmin != null) { notificationAdmin.destroy(); } + if (vcAdmin != null) { + vcAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java index e728be6085..1dca3ba551 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -169,6 +170,12 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + //TODO: version-control + return null; + } + @Override public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic(), diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbVersionControlQueueFactory.java new file mode 100644 index 0000000000..efc433c86e --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbVersionControlQueueFactory.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.queue.provider; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueAdmin; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.rabbitmq.TbRabbitMqAdmin; +import org.thingsboard.server.queue.rabbitmq.TbRabbitMqConsumerTemplate; +import org.thingsboard.server.queue.rabbitmq.TbRabbitMqProducerTemplate; +import org.thingsboard.server.queue.rabbitmq.TbRabbitMqQueueArguments; +import org.thingsboard.server.queue.rabbitmq.TbRabbitMqSettings; +import org.thingsboard.server.queue.settings.TbQueueCoreSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; + +import javax.annotation.PreDestroy; + +@Component +@ConditionalOnExpression("'${queue.type:null}'=='rabbitmq' && '${service.type:null}'=='tb-vc-executor'") +public class RabbitMqTbVersionControlQueueFactory implements TbVersionControlQueueFactory { + + private final TbRabbitMqSettings rabbitMqSettings; + private final TbQueueCoreSettings coreSettings; + private final TbQueueVersionControlSettings vcSettings; + + private final TbQueueAdmin coreAdmin; + private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin vcAdmin; + + public RabbitMqTbVersionControlQueueFactory(TbRabbitMqSettings rabbitMqSettings, + TbQueueCoreSettings coreSettings, + TbQueueVersionControlSettings vcSettings, + TbRabbitMqQueueArguments queueArguments + ) { + this.rabbitMqSettings = rabbitMqSettings; + this.coreSettings = coreSettings; + this.vcSettings = vcSettings; + + this.coreAdmin = new TbRabbitMqAdmin(this.rabbitMqSettings, queueArguments.getCoreArgs()); + this.notificationAdmin = new TbRabbitMqAdmin(this.rabbitMqSettings, queueArguments.getNotificationsArgs()); + this.vcAdmin = new TbRabbitMqAdmin(this.rabbitMqSettings, queueArguments.getVcArgs()); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, coreSettings.getTopic()); + } + + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(vcAdmin, rabbitMqSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + + @PreDestroy + private void destroy() { + if (coreAdmin != null) { + coreAdmin.destroy(); + } + if (notificationAdmin != null) { + notificationAdmin.destroy(); + } + if (vcAdmin != null) { + vcAdmin.destroy(); + } + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java index 88bb0a4045..a740a6d1a4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -50,13 +51,14 @@ import org.thingsboard.server.queue.settings.TbQueueRemoteJsInvokeSettings; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; import javax.annotation.PreDestroy; import java.nio.charset.StandardCharsets; @Component @ConditionalOnExpression("'${queue.type:null}'=='service-bus' && '${service.type:null}'=='monolith'") -public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory { +public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngineQueueFactory, TbVersionControlQueueFactory { private final NotificationsTopicService notificationsTopicService; private final TbQueueCoreSettings coreSettings; @@ -66,12 +68,14 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul private final TbQueueTransportNotificationSettings transportNotificationSettings; private final TbServiceBusSettings serviceBusSettings; private final TbQueueRemoteJsInvokeSettings jsInvokeSettings; + private final TbQueueVersionControlSettings vcSettings; private final TbQueueAdmin coreAdmin; private final TbQueueAdmin ruleEngineAdmin; private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin transportApiAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin vcAdmin; public ServiceBusMonolithQueueFactory(NotificationsTopicService notificationsTopicService, TbQueueCoreSettings coreSettings, TbQueueRuleEngineSettings ruleEngineSettings, @@ -80,6 +84,7 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul TbQueueTransportNotificationSettings transportNotificationSettings, TbServiceBusSettings serviceBusSettings, TbQueueRemoteJsInvokeSettings jsInvokeSettings, + TbQueueVersionControlSettings vcSettings, TbServiceBusQueueConfigs serviceBusQueueConfigs) { this.notificationsTopicService = notificationsTopicService; this.coreSettings = coreSettings; @@ -89,12 +94,14 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul this.transportNotificationSettings = transportNotificationSettings; this.serviceBusSettings = serviceBusSettings; this.jsInvokeSettings = jsInvokeSettings; + this.vcSettings = vcSettings; this.coreAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getCoreConfigs()); this.ruleEngineAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getRuleEngineConfigs()); this.jsExecutorAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getJsExecutorConfigs()); this.transportApiAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getTransportApiConfigs()); this.notificationAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getNotificationsConfigs()); + this.vcAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getVcConfigs()); } @Override @@ -122,6 +129,13 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, coreSettings.getTopic()); } + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(vcAdmin, serviceBusSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + @Override public TbQueueConsumer> createToRuleEngineMsgConsumer(Queue configuration) { return new TbServiceBusConsumerTemplate<>(ruleEngineAdmin, serviceBusSettings, configuration.getTopic(), @@ -204,6 +218,11 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + return new TbServiceBusProducerTemplate<>(vcAdmin, serviceBusSettings, vcSettings.getTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { @@ -221,5 +240,8 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul if (notificationAdmin != null) { notificationAdmin.destroy(); } + if (vcAdmin != null) { + vcAdmin.destroy(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java index e1eb41b2b2..5b471b2830 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateServiceMsg; @@ -191,6 +192,12 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); } + @Override + public TbQueueProducer> createVersionControlMsgProducer() { + //TODO: version-control + return null; + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbVersionControlQueueFactory.java new file mode 100644 index 0000000000..75e788af16 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbVersionControlQueueFactory.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.queue.provider; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueAdmin; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusAdmin; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusConsumerTemplate; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusProducerTemplate; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusQueueConfigs; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusSettings; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.settings.TbQueueCoreSettings; +import org.thingsboard.server.queue.settings.TbQueueVersionControlSettings; + +import javax.annotation.PreDestroy; + +@Component +@ConditionalOnExpression("'${queue.type:null}'=='service-bus' && '${service.type:null}'=='tb-vc-executor'") +public class ServiceBusTbVersionControlQueueFactory implements TbVersionControlQueueFactory { + + private final TbServiceBusSettings serviceBusSettings; + private final TbQueueCoreSettings coreSettings; + private final TbQueueVersionControlSettings vcSettings; + + private final TbQueueAdmin coreAdmin; + private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin vcAdmin; + + public ServiceBusTbVersionControlQueueFactory(TbServiceBusSettings serviceBusSettings, + TbQueueCoreSettings coreSettings, + TbQueueVersionControlSettings vcSettings, + TbServiceBusQueueConfigs serviceBusQueueConfigs + ) { + this.serviceBusSettings = serviceBusSettings; + this.coreSettings = coreSettings; + this.vcSettings = vcSettings; + + this.coreAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getCoreConfigs()); + this.notificationAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getNotificationsConfigs()); + this.vcAdmin = new TbServiceBusAdmin(serviceBusSettings, serviceBusQueueConfigs.getVcConfigs()); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + return new TbServiceBusProducerTemplate<>(notificationAdmin, serviceBusSettings, coreSettings.getTopic()); + } + + @Override + public TbQueueConsumer> createToVersionControlMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(vcAdmin, serviceBusSettings, vcSettings.getTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToVersionControlServiceMsg.parseFrom(msg.getData()), msg.getHeaders()) + ); + } + + @PreDestroy + private void destroy() { + if (coreAdmin != null) { + coreAdmin.destroy(); + } + if (notificationAdmin != null) { + notificationAdmin.destroy(); + } + if (vcAdmin != null) { + vcAdmin.destroy(); + } + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index 4debe1e280..aadefa0292 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -20,6 +20,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateSer import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; @@ -122,4 +123,11 @@ public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory { TbQueueProducer> createTransportApiResponseProducer(); TbQueueRequestTemplate, TbProtoQueueMsg> createRemoteJsRequestTemplate(); + + /** + * Used to push messages to instances of TB Version Control Service + * + * @return + */ + TbQueueProducer> createVersionControlMsgProducer(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java index ef0b0c38e9..ed7de35274 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java @@ -22,6 +22,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -39,6 +40,7 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { private TbQueueProducer> toRuleEngineNotifications; private TbQueueProducer> toTbCoreNotifications; private TbQueueProducer> toUsageStats; + private TbQueueProducer> toVersionControl; public TbCoreQueueProducerProvider(TbCoreQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -52,6 +54,7 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { this.toRuleEngineNotifications = tbQueueProvider.createRuleEngineNotificationsMsgProducer(); this.toTbCoreNotifications = tbQueueProvider.createTbCoreNotificationsMsgProducer(); this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); + this.toVersionControl = tbQueueProvider.createVersionControlMsgProducer(); } @Override @@ -83,4 +86,9 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { public TbQueueProducer> getTbUsageStatsMsgProducer() { return toUsageStats; } + + @Override + public TbQueueProducer> getTbVersionControlMsgProducer() { + return toVersionControl; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java index 19ebb4666e..19046c5a2f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java @@ -21,6 +21,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -70,4 +71,11 @@ public interface TbQueueProducerProvider { * @return */ TbQueueProducer> getTbUsageStatsMsgProducer(); + + /** + * Used to push messages to other instances of TB Core Service + * + * @return + */ + TbQueueProducer> getTbVersionControlMsgProducer(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java index 26f9df069e..c21ae99b8a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java @@ -17,6 +17,7 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -83,4 +84,9 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { public TbQueueProducer> getTbUsageStatsMsgProducer() { return toUsageStats; } + + @Override + public TbQueueProducer> getTbVersionControlMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Rule Engine!"); + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java index 3132ed684f..f9cc139559 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java @@ -17,6 +17,7 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -73,6 +74,11 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } + @Override + public TbQueueProducer> getTbVersionControlMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Transport!"); + } + @Override public TbQueueProducer> getTbUsageStatsMsgProducer() { return toUsageStats; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java new file mode 100644 index 0000000000..c8ffc1da0d --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlProducerProvider.java @@ -0,0 +1,84 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.provider; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Service; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +import javax.annotation.PostConstruct; + +@Service +@ConditionalOnExpression("'${service.type:null}'=='tb-vc-executor'") +public class TbVersionControlProducerProvider implements TbQueueProducerProvider { + + private final TbVersionControlQueueFactory tbQueueProvider; + private TbQueueProducer> toTbCoreNotifications; + private TbQueueProducer> toUsageStats; + + public TbVersionControlProducerProvider(TbVersionControlQueueFactory tbQueueProvider) { + this.tbQueueProvider = tbQueueProvider; + } + + @PostConstruct + public void init() { + this.toTbCoreNotifications = tbQueueProvider.createTbCoreNotificationsMsgProducer(); + this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); + } + + @Override + public TbQueueProducer> getTransportNotificationsMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); + } + + @Override + public TbQueueProducer> getRuleEngineMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); + } + + @Override + public TbQueueProducer> getTbCoreMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); + } + + @Override + public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); + } + + @Override + public TbQueueProducer> getTbCoreNotificationsMsgProducer() { + return toTbCoreNotifications; + } + + @Override + public TbQueueProducer> getTbVersionControlMsgProducer() { + throw new RuntimeException("Not Implemented! Should not be used by Version Control Service!"); + } + + @Override + public TbQueueProducer> getTbUsageStatsMsgProducer() { + return toUsageStats; + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlQueueFactory.java new file mode 100644 index 0000000000..70b16afc68 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbVersionControlQueueFactory.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.provider; + +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +/** + * Responsible for initialization of various Producers and Consumers used by TB Version Control Node. + * Implementation Depends on the queue queue.type from yml or TB_QUEUE_TYPE environment variable + */ +public interface TbVersionControlQueueFactory extends TbUsageStatsClientQueueFactory { + + /** + * Used to push notifications to other instances of TB Core Service + * + * @return + */ + TbQueueProducer> createTbCoreNotificationsMsgProducer(); + + /** + * Used to consume messages from TB Core Service + * + * @return + */ + TbQueueConsumer> createToVersionControlMsgConsumer(); + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java index e849d5b4f3..1f5430a0bb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubSubscriptionSettings.java @@ -19,6 +19,7 @@ import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import javax.annotation.PostConstruct; import java.util.HashMap; @@ -27,16 +28,18 @@ import java.util.Map; @Component @ConditionalOnExpression("'${queue.type:null}'=='pubsub'") public class TbPubSubSubscriptionSettings { - @Value("${queue.pubsub.queue-properties.core}") + @Value("${queue.pubsub.queue-properties.core:}") private String coreProperties; - @Value("${queue.pubsub.queue-properties.rule-engine}") + @Value("${queue.pubsub.queue-properties.rule-engine:}") private String ruleEngineProperties; - @Value("${queue.pubsub.queue-properties.transport-api}") + @Value("${queue.pubsub.queue-properties.transport-api:}") private String transportApiProperties; - @Value("${queue.pubsub.queue-properties.notifications}") + @Value("${queue.pubsub.queue-properties.notifications:}") private String notificationsProperties; - @Value("${queue.pubsub.queue-properties.js-executor}") + @Value("${queue.pubsub.queue-properties.js-executor:}") private String jsExecutorProperties; + @Value("${queue.pubsub.queue-properties.version-control:}") + private String vcProperties; @Getter private Map coreSettings; @@ -48,6 +51,8 @@ public class TbPubSubSubscriptionSettings { private Map notificationsSettings; @Getter private Map jsExecutorSettings; + @Getter + private Map vcSettings; @PostConstruct private void init() { @@ -56,15 +61,18 @@ public class TbPubSubSubscriptionSettings { transportApiSettings = getSettings(transportApiProperties); notificationsSettings = getSettings(notificationsProperties); jsExecutorSettings = getSettings(jsExecutorProperties); + vcSettings = getSettings(vcProperties); } private Map getSettings(String properties) { Map configs = new HashMap<>(); - for (String property : properties.split(";")) { - int delimiterPosition = property.indexOf(":"); - String key = property.substring(0, delimiterPosition); - String value = property.substring(delimiterPosition + 1); - configs.put(key, value); + if (StringUtils.isNotEmpty(properties)) { + for (String property : properties.split(";")) { + int delimiterPosition = property.indexOf(":"); + String key = property.substring(0, delimiterPosition); + String value = property.substring(delimiterPosition + 1); + configs.put(key, value); + } } return configs; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java b/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java index d2523f3b8f..af3602fd73 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqQueueArguments.java @@ -19,6 +19,7 @@ import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import javax.annotation.PostConstruct; import java.util.HashMap; @@ -28,16 +29,18 @@ import java.util.regex.Pattern; @Component @ConditionalOnExpression("'${queue.type:null}'=='rabbitmq'") public class TbRabbitMqQueueArguments { - @Value("${queue.rabbitmq.queue-properties.core}") + @Value("${queue.rabbitmq.queue-properties.core:}") private String coreProperties; - @Value("${queue.rabbitmq.queue-properties.rule-engine}") + @Value("${queue.rabbitmq.queue-properties.rule-engine:}") private String ruleEngineProperties; - @Value("${queue.rabbitmq.queue-properties.transport-api}") + @Value("${queue.rabbitmq.queue-properties.transport-api:}") private String transportApiProperties; - @Value("${queue.rabbitmq.queue-properties.notifications}") + @Value("${queue.rabbitmq.queue-properties.notifications:}") private String notificationsProperties; - @Value("${queue.rabbitmq.queue-properties.js-executor}") + @Value("${queue.rabbitmq.queue-properties.js-executor:}") private String jsExecutorProperties; + @Value("${queue.rabbitmq.queue-properties.version-control:}") + private String vcProperties; @Getter private Map coreArgs; @@ -49,6 +52,8 @@ public class TbRabbitMqQueueArguments { private Map notificationsArgs; @Getter private Map jsExecutorArgs; + @Getter + private Map vcArgs; @PostConstruct private void init() { @@ -57,15 +62,18 @@ public class TbRabbitMqQueueArguments { transportApiArgs = getArgs(transportApiProperties); notificationsArgs = getArgs(notificationsProperties); jsExecutorArgs = getArgs(jsExecutorProperties); + vcArgs = getArgs(vcProperties); } private Map getArgs(String properties) { Map configs = new HashMap<>(); - for (String property : properties.split(";")) { - int delimiterPosition = property.indexOf(":"); - String key = property.substring(0, delimiterPosition); - String strValue = property.substring(delimiterPosition + 1); - configs.put(key, getObjectValue(strValue)); + if (StringUtils.isNotEmpty(properties)) { + for (String property : properties.split(";")) { + int delimiterPosition = property.indexOf(":"); + String key = property.substring(0, delimiterPosition); + String strValue = property.substring(delimiterPosition + 1); + configs.put(key, getObjectValue(strValue)); + } } return configs; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java index 33e6552eb9..f64143b14c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java @@ -17,8 +17,10 @@ package org.thingsboard.server.queue.settings; import lombok.Data; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +@Lazy @Data @Component public class TbQueueCoreSettings { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java index b930d04312..f4d62bad1d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java @@ -17,8 +17,10 @@ package org.thingsboard.server.queue.settings; import lombok.Data; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +@Lazy @Data @Component public class TbQueueRemoteJsInvokeSettings { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRuleEngineSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRuleEngineSettings.java index eee70e9e22..57131f6b62 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRuleEngineSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRuleEngineSettings.java @@ -17,8 +17,10 @@ package org.thingsboard.server.queue.settings; import lombok.Data; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +@Lazy @Data @Component public class TbQueueRuleEngineSettings { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportApiSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportApiSettings.java index 0ad9414bd8..a4dfe86bd1 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportApiSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportApiSettings.java @@ -17,8 +17,10 @@ package org.thingsboard.server.queue.settings; import lombok.Data; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +@Lazy @Data @Component public class TbQueueTransportApiSettings { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportNotificationSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportNotificationSettings.java index d4c0064692..e2a10b31b3 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportNotificationSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueTransportNotificationSettings.java @@ -17,8 +17,10 @@ package org.thingsboard.server.queue.settings; import lombok.Data; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; +@Lazy @Data @Component public class TbQueueTransportNotificationSettings { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueVersionControlSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueVersionControlSettings.java new file mode 100644 index 0000000000..3a7a5602d5 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueVersionControlSettings.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.queue.settings; + +import lombok.Data; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; + +@Lazy +@Data +@Component +public class TbQueueVersionControlSettings { + + @Value("${queue.vc.topic:tb_version_control}") + private String topic; + + @Value("${queue.vc.usage-stats-topic:tb_usage_stats}") + private String usageStatsTopic; + + @Value("${queue.vc.partitions:10}") + private int partitions; +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java index 0cf02348f3..2e28b56076 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java @@ -20,6 +20,7 @@ import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import javax.annotation.PostConstruct; import java.util.HashMap; @@ -28,16 +29,20 @@ import java.util.Map; @Component @ConditionalOnExpression("'${queue.type:null}'=='aws-sqs'") public class TbAwsSqsQueueAttributes { - @Value("${queue.aws-sqs.queue-properties.core}") + @Value("${queue.aws-sqs.queue-properties.core:}") private String coreProperties; - @Value("${queue.aws-sqs.queue-properties.rule-engine}") + @Value("${queue.aws-sqs.queue-properties.rule-engine:}") private String ruleEngineProperties; - @Value("${queue.aws-sqs.queue-properties.transport-api}") + @Value("${queue.aws-sqs.queue-properties.transport-api:}") private String transportApiProperties; - @Value("${queue.aws-sqs.queue-properties.notifications}") + @Value("${queue.aws-sqs.queue-properties.notifications:}") private String notificationsProperties; - @Value("${queue.aws-sqs.queue-properties.js-executor}") + @Value("${queue.aws-sqs.queue-properties.js-executor:}") private String jsExecutorProperties; + @Value("${queue.aws-sqs.queue-properties.ota-updates:}") + private String otaProperties; + @Value("${queue.aws-sqs.queue-properties.version-control:}") + private String vcProperties; @Getter private Map coreAttributes; @@ -49,6 +54,10 @@ public class TbAwsSqsQueueAttributes { private Map notificationsAttributes; @Getter private Map jsExecutorAttributes; + @Getter + private Map otaAttributes; + @Getter + private Map vcAttributes; private final Map defaultAttributes = new HashMap<>(); @@ -61,18 +70,21 @@ public class TbAwsSqsQueueAttributes { transportApiAttributes = getConfigs(transportApiProperties); notificationsAttributes = getConfigs(notificationsProperties); jsExecutorAttributes = getConfigs(jsExecutorProperties); + otaAttributes = getConfigs(otaProperties); + vcAttributes = getConfigs(vcProperties); } private Map getConfigs(String properties) { - Map configs = new HashMap<>(); - for (String property : properties.split(";")) { - int delimiterPosition = property.indexOf(":"); - String key = property.substring(0, delimiterPosition); - String value = property.substring(delimiterPosition + 1); - validateAttributeName(key); - configs.put(key, value); + Map configs = new HashMap<>(defaultAttributes); + if (StringUtils.isNotEmpty(properties)) { + for (String property : properties.split(";")) { + int delimiterPosition = property.indexOf(":"); + String key = property.substring(0, delimiterPosition); + String value = property.substring(delimiterPosition + 1); + validateAttributeName(key); + configs.put(key, value); + } } - configs.putAll(defaultAttributes); return configs; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/DataDecodingEncodingService.java similarity index 93% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java rename to common/queue/src/main/java/org/thingsboard/server/queue/util/DataDecodingEncodingService.java index ed0572e8e5..8ebcc39cab 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/DataDecodingEncodingService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/DataDecodingEncodingService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.util; +package org.thingsboard.server.queue.util; import java.util.Optional; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/ProtoWithFSTService.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java similarity index 76% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/ProtoWithFSTService.java rename to common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java index 1c5eec383c..93d37560e2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/util/ProtoWithFSTService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/ProtoWithFSTService.java @@ -13,11 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.util; +package org.thingsboard.server.queue.util; import lombok.extern.slf4j.Slf4j; import org.nustaq.serialization.FSTConfiguration; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.FSTUtils; import java.util.Optional; @@ -25,23 +26,23 @@ import java.util.Optional; @Service public class ProtoWithFSTService implements DataDecodingEncodingService { - private final FSTConfiguration config = FSTConfiguration.createDefaultConfiguration(); + public static final FSTConfiguration CONFIG = FSTConfiguration.createDefaultConfiguration(); @Override public Optional decode(byte[] byteArray) { try { - @SuppressWarnings("unchecked") - T msg = byteArray != null && byteArray.length > 0 ? (T) config.asObject(byteArray) : null; - return Optional.ofNullable(msg); + return Optional.ofNullable(FSTUtils.decode(byteArray)); } catch (IllegalArgumentException e) { log.error("Error during deserialization message, [{}]", e.getMessage()); return Optional.empty(); } } + @Override public byte[] encode(T msq) { - return config.asByteArray(msq); + return FSTUtils.encode(msq); } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbVersionControlComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbVersionControlComponent.java new file mode 100644 index 0000000000..e72fe60b6e --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbVersionControlComponent.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.queue.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnExpression("'${service.type:null}'=='monolith' || '${service.type:null}'=='tb-vc-executor'") +public @interface TbVersionControlComponent { +} diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/discovery/QueueKeyTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/QueueKeyTest.java new file mode 100644 index 0000000000..6fcb365f09 --- /dev/null +++ b/common/queue/src/test/java/org/thingsboard/server/queue/discovery/QueueKeyTest.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.discovery; + +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +@Slf4j +class QueueKeyTest { + + @Test + void testToStringSystemTenant() { + QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "Main", TenantId.SYS_TENANT_ID); + log.info("The queue key is {}",queueKey); + assertThat(queueKey.toString()).isEqualTo("QK(Main,TB_RULE_ENGINE,system)"); + } + + @Test + void testToStringCustomTenant() { + TenantId tenantId = TenantId.fromUUID(UUID.fromString("3ebd39eb-43d4-4911-a818-cdbf8d508f88")); + QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "Main", tenantId); + log.info("The queue key is {}",queueKey); + assertThat(queueKey.toString()).isEqualTo("QK(Main,TB_RULE_ENGINE,3ebd39eb-43d4-4911-a818-cdbf8d508f88)"); + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java index c75de25267..a10c3e4969 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java @@ -24,14 +24,11 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; -import java.util.stream.Collectors; @TbSnmpTransportComponent @Service diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 8b3f9feb22..51373e2a4a 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -64,10 +64,6 @@ com.google.code.gson gson - - de.ruedigermoeller - fst - org.slf4j slf4j-api diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java index b79306199f..2b0f73343d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbTransportComponent; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java index 666941b143..b4187432e8 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportResourceCache.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbTransportComponent; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 9f6ddc87a4..a63e4a01a2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -69,7 +69,7 @@ import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGateway import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -901,7 +901,7 @@ public class DefaultTransportService implements TransportService { if (EntityType.DEVICE_PROFILE.equals(entityType)) { DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); if (deviceProfile != null) { - log.info("On device profile update: {}", deviceProfile); + log.debug("On device profile update: {}", deviceProfile); onProfileUpdate(deviceProfile); } } else if (EntityType.TENANT_PROFILE.equals(entityType)) { @@ -1088,7 +1088,7 @@ public class DefaultTransportService implements TransportService { } private void sendToRuleEngine(TenantId tenantId, TbMsg tbMsg, TbQueueCallback callback) { - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueId(), tenantId, tbMsg.getOriginator()); + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, tbMsg.getOriginator()); if (log.isTraceEnabled()) { log.trace("[{}][{}] Pushing to topic {} message {}", tenantId, tbMsg.getOriginator(), tpi.getFullTopicName(), tbMsg); } @@ -1105,18 +1105,18 @@ public class DefaultTransportService implements TransportService { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; - QueueId queueId; + String queueName; if (deviceProfile == null) { log.warn("[{}] Device profile is null!", deviceProfileId); ruleChainId = null; - queueId = null; + queueName = null; } else { ruleChainId = deviceProfile.getDefaultRuleChainId(); - queueId = deviceProfile.getDefaultQueueId(); + queueName = deviceProfile.getDefaultQueueName(); } - TbMsg tbMsg = TbMsg.newMsg(queueId, sessionMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); + TbMsg tbMsg = TbMsg.newMsg(queueName, sessionMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); sendToRuleEngine(tenantId, tbMsg, callback); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 4cfefb7a6f..bb7f8f3f4d 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -29,7 +29,7 @@ import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbTransportComponent; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java index fa66a355eb..6afc07f556 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/TransportTenantRoutingInfoService.java @@ -38,7 +38,7 @@ public class TransportTenantRoutingInfoService implements TenantRoutingInfoServi @Override public TenantRoutingInfo getRoutingInfo(TenantId tenantId) { TenantProfile profile = tenantProfileCache.get(tenantId); - return new TenantRoutingInfo(tenantId, profile.isIsolatedTbCore(), profile.isIsolatedTbRuleEngine()); + return new TenantRoutingInfo(tenantId, profile.isIsolatedTbRuleEngine()); } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java b/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java index edace3cee5..f7da0b07a7 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java +++ b/common/util/src/main/java/org/thingsboard/common/util/AbstractListeningExecutor.java @@ -49,6 +49,10 @@ public abstract class AbstractListeningExecutor implements ListeningExecutor { return service.submit(task); } + public ListenableFuture executeAsync(Runnable task) { + return service.submit(task); + } + @Override public void execute(Runnable command) { service.execute(command); diff --git a/common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java b/common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java index 7e39f9a273..adcffb8d21 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/CollectionsUtil.java @@ -39,4 +39,12 @@ public class CollectionsUtil { return isNotEmpty(collection) && collection.contains(element); } + public static int countNonNull(T[] array) { + int count = 0; + for (T t : array) { + if (t != null) count++; + } + return count; + } + } diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index f9df92966b..9c4b68d3c0 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -18,13 +18,20 @@ package org.thingsboard.common.util; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.UUID; +import java.util.function.UnaryOperator; /** * Created by Valerii Sosliuk on 5/12/2017. @@ -32,6 +39,11 @@ import java.util.Set; public class JacksonUtil { public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public static final ObjectMapper PRETTY_SORTED_JSON_MAPPER = JsonMapper.builder() + .enable(SerializationFeature.INDENT_OUTPUT) + .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .build(); public static T convertValue(Object fromValue, Class toValueType) { try { @@ -96,6 +108,14 @@ public class JacksonUtil { } } + public static String toPrettyString(Object o) { + try { + return PRETTY_SORTED_JSON_MAPPER.writeValueAsString(o); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + public static JsonNode toJsonNode(String value) { if (value == null || value.isEmpty()) { return null; @@ -137,4 +157,59 @@ public class JacksonUtil { + value + " cannot be transformed to a String", e); } } + + + public static JsonNode getSafely(JsonNode node, String... path) { + if (node == null) { + return null; + } + for (String p : path) { + if (!node.has(p)) { + return null; + } else { + node = node.get(p); + } + } + return node; + } + + public static void replaceUuidsRecursively(JsonNode node, Set skipFieldsSet, UnaryOperator replacer) { + if (node == null) { + return; + } + if (node.isObject()) { + ObjectNode objectNode = (ObjectNode) node; + List fieldNames = new ArrayList<>(objectNode.size()); + objectNode.fieldNames().forEachRemaining(fieldNames::add); + for (String fieldName : fieldNames) { + if (skipFieldsSet.contains(fieldName)) { + continue; + } + var child = objectNode.get(fieldName); + if (child.isObject() || child.isArray()) { + replaceUuidsRecursively(child, skipFieldsSet, replacer); + } else if (child.isTextual()) { + String text = child.asText(); + String newText = RegexUtils.replace(text, RegexUtils.UUID_PATTERN, uuid -> replacer.apply(UUID.fromString(uuid)).toString()); + if (!text.equals(newText)) { + objectNode.put(fieldName, newText); + } + } + } + } else if (node.isArray()) { + ArrayNode array = (ArrayNode) node; + for (int i = 0; i < array.size(); i++) { + JsonNode arrayElement = array.get(i); + if (arrayElement.isObject() || arrayElement.isArray()) { + replaceUuidsRecursively(arrayElement, skipFieldsSet, replacer); + } else if (arrayElement.isTextual()) { + String text = arrayElement.asText(); + String newText = RegexUtils.replace(text, RegexUtils.UUID_PATTERN, uuid -> replacer.apply(UUID.fromString(uuid)).toString()); + if (!text.equals(newText)) { + array.set(i, newText); + } + } + } + } + } } diff --git a/common/util/src/main/java/org/thingsboard/common/util/ListeningExecutor.java b/common/util/src/main/java/org/thingsboard/common/util/ListeningExecutor.java index 2726868f19..e63613d935 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/ListeningExecutor.java +++ b/common/util/src/main/java/org/thingsboard/common/util/ListeningExecutor.java @@ -24,8 +24,19 @@ public interface ListeningExecutor extends Executor { ListenableFuture executeAsync(Callable task); + default ListenableFuture executeAsync(Runnable task) { + return executeAsync(() -> { + task.run(); + return null; + }); + } + default ListenableFuture submit(Callable task) { return executeAsync(task); } + default ListenableFuture submit(Runnable task) { + return executeAsync(task); + } + } diff --git a/common/util/src/main/java/org/thingsboard/common/util/RegexUtils.java b/common/util/src/main/java/org/thingsboard/common/util/RegexUtils.java new file mode 100644 index 0000000000..af968a6bff --- /dev/null +++ b/common/util/src/main/java/org/thingsboard/common/util/RegexUtils.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.common.util; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.function.UnaryOperator; +import java.util.regex.Pattern; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class RegexUtils { + + public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); + + + public static String replace(String s, Pattern pattern, UnaryOperator replacer) { + return pattern.matcher(s).replaceAll(matchResult -> { + return replacer.apply(matchResult.group()); + }); + } + +} diff --git a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java index 13a0d466ac..d8b4cbf720 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java +++ b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java @@ -28,12 +28,23 @@ import org.springframework.util.StopWatch; * */ public class TbStopWatch extends StopWatch { - public static TbStopWatch startNew(){ + public static TbStopWatch create(){ TbStopWatch stopWatch = new TbStopWatch(); stopWatch.start(); return stopWatch; } + public static TbStopWatch create(String taskName){ + TbStopWatch stopWatch = new TbStopWatch(); + stopWatch.start(taskName); + return stopWatch; + } + + public void startNew(String taskName){ + stop(); + start(taskName); + } + public long stopAndGetTotalTimeMillis(){ stop(); return getTotalTimeMillis(); diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml new file mode 100644 index 0000000000..998ae0879b --- /dev/null +++ b/common/version-control/pom.xml @@ -0,0 +1,125 @@ + + + 4.0.0 + + org.thingsboard + 3.4.0-SNAPSHOT + common + + org.thingsboard.common + version-control + jar + + Thingsboard Server Version Control API + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + + + + + org.thingsboard.common + data + + + org.thingsboard.common + queue + + + org.springframework + spring-core + + + org.springframework + spring-context-support + + + org.springframework + spring-context + + + org.springframework.boot + spring-boot-starter-web + provided + + + javax.annotation + javax.annotation-api + + + com.google.guava + guava + + + com.fasterxml.jackson.core + jackson-databind + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.eclipse.jgit + org.eclipse.jgit + + + org.eclipse.jgit + org.eclipse.jgit.ssh.apache + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.awaitility + awaitility + test + + + + + + thingsboard-repo-deploy + ThingsBoard Repo Deployment + https://repo.thingsboard.io/artifactory/libs-release-public + + + + diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/ClusterVersionControlService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/ClusterVersionControlService.java new file mode 100644 index 0000000000..0823dd06e6 --- /dev/null +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/ClusterVersionControlService.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import org.springframework.context.ApplicationListener; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; + +public interface ClusterVersionControlService extends ApplicationListener { +} diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java new file mode 100644 index 0000000000..eb7a3d20e4 --- /dev/null +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultClusterVersionControlService.java @@ -0,0 +1,583 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.CollectionsUtil; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.AddMsg; +import org.thingsboard.server.gen.transport.TransportProtos.BranchInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.CommitRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.CommitResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.DeleteMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntitiesContentRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntitiesContentResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntityContentRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntityContentResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntityVersionProto; +import org.thingsboard.server.gen.transport.TransportProtos.ListBranchesRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ListBranchesResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ListEntitiesRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ListEntitiesResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ListVersionsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ListVersionsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PrepareMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.VersionControlResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.VersionedEntityInfoProto; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.NotificationsTopicService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.provider.TbVersionControlQueueFactory; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.queue.util.TbVersionControlComponent; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.thingsboard.server.service.sync.vc.DefaultGitRepositoryService.fromRelativePath; + +@Slf4j +@TbVersionControlComponent +@Service +@RequiredArgsConstructor +public class DefaultClusterVersionControlService extends TbApplicationEventListener implements ClusterVersionControlService { + + private final PartitionService partitionService; + private final TbQueueProducerProvider producerProvider; + private final TbVersionControlQueueFactory queueFactory; + private final DataDecodingEncodingService encodingService; + private final GitRepositoryService vcService; + private final NotificationsTopicService notificationsTopicService; + + private final ConcurrentMap tenantRepoLocks = new ConcurrentHashMap<>(); + private final Map pendingCommitMap = new HashMap<>(); + + private volatile ExecutorService consumerExecutor; + private volatile TbQueueConsumer> consumer; + private volatile TbQueueProducer> producer; + private volatile boolean stopped = false; + + @Value("${queue.vc.poll-interval:25}") + private long pollDuration; + @Value("${queue.vc.pack-processing-timeout:60000}") + private long packProcessingTimeout; + @Value("${vc.git.io_pool_size:3}") + private int ioPoolSize; + @Value("${queue.vc.msg-chunk-size:500000}") + private int msgChunkSize; + + //We need to manually manage the threads since tasks for particular tenant need to be processed sequentially. + private final List ioThreads = new ArrayList<>(); + + + @PostConstruct + public void init() { + consumerExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("vc-consumer")); + var threadFactory = ThingsBoardThreadFactory.forName("vc-io-thread"); + for (int i = 0; i < ioPoolSize; i++) { + ioThreads.add(MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(threadFactory))); + } + producer = producerProvider.getTbCoreNotificationsMsgProducer(); + consumer = queueFactory.createToVersionControlMsgConsumer(); + } + + @PreDestroy + public void stop() { + stopped = true; + if (consumer != null) { + consumer.unsubscribe(); + } + if (consumerExecutor != null) { + consumerExecutor.shutdownNow(); + } + ioThreads.forEach(ExecutorService::shutdownNow); + } + + @Override + protected void onTbApplicationEvent(PartitionChangeEvent event) { + for (TenantId tenantId : vcService.getActiveRepositoryTenants()) { + if (!partitionService.resolve(ServiceType.TB_VC_EXECUTOR, tenantId, tenantId).isMyPartition()) { + var lock = getRepoLock(tenantId); + lock.lock(); + try { + pendingCommitMap.remove(tenantId); + vcService.clearRepository(tenantId); + } catch (Exception e) { + log.warn("[{}] Failed to cleanup the tenant repository", tenantId, e); + } finally { + lock.unlock(); + } + } + } + consumer.subscribe(event.getPartitions()); + } + + @Override + protected boolean filterTbApplicationEvent(PartitionChangeEvent event) { + return ServiceType.TB_VC_EXECUTOR.equals(event.getServiceType()); + } + + @EventListener(ApplicationReadyEvent.class) + @Order(value = 2) + public void onApplicationEvent(ApplicationReadyEvent event) { + consumerExecutor.execute(() -> consumerLoop(consumer)); + } + + void consumerLoop(TbQueueConsumer> consumer) { + while (!stopped && !consumer.isStopped()) { + List> futures = new ArrayList<>(); + try { + List> msgs = consumer.poll(pollDuration); + if (msgs.isEmpty()) { + continue; + } + for (TbProtoQueueMsg msgWrapper : msgs) { + ToVersionControlServiceMsg msg = msgWrapper.getValue(); + var ctx = new VersionControlRequestCtx(msg, msg.hasClearRepositoryRequest() ? null : getEntitiesVersionControlSettings(msg)); + long startTs = System.currentTimeMillis(); + log.trace("[{}][{}] RECEIVED task: {}", ctx.getTenantId(), ctx.getRequestId(), msg); + int threadIdx = Math.abs(ctx.getTenantId().hashCode() % ioPoolSize); + ListenableFuture future = ioThreads.get(threadIdx).submit(() -> processMessage(ctx, msg)); + logTaskExecution(ctx, future, startTs); + futures.add(future); + } + try { + Futures.allAsList(futures).get(packProcessingTimeout, TimeUnit.MILLISECONDS); + } catch (TimeoutException e) { + log.info("Timeout for processing the version control tasks.", e); + } + consumer.commit(); + } catch (Exception e) { + if (!stopped) { + log.warn("Failed to obtain version control requests from queue.", e); + try { + Thread.sleep(pollDuration); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new version control messages", e2); + } + } + } + } + log.info("TB Version Control request consumer stopped."); + } + + private Void processMessage(VersionControlRequestCtx ctx, ToVersionControlServiceMsg msg) { + var lock = getRepoLock(ctx.getTenantId()); + lock.lock(); + try { + if (msg.hasClearRepositoryRequest()) { + handleClearRepositoryCommand(ctx); + } else { + if (msg.hasTestRepositoryRequest()) { + handleTestRepositoryCommand(ctx); + } else if (msg.hasInitRepositoryRequest()) { + handleInitRepositoryCommand(ctx); + } else { + var currentSettings = vcService.getRepositorySettings(ctx.getTenantId()); + var newSettings = ctx.getSettings(); + if (!newSettings.equals(currentSettings)) { + vcService.initRepository(ctx.getTenantId(), ctx.getSettings()); + } + if (msg.hasCommitRequest()) { + handleCommitRequest(ctx, msg.getCommitRequest()); + } else if (msg.hasListBranchesRequest()) { + vcService.fetch(ctx.getTenantId()); + handleListBranches(ctx, msg.getListBranchesRequest()); + } else if (msg.hasListEntitiesRequest()) { + handleListEntities(ctx, msg.getListEntitiesRequest()); + } else if (msg.hasListVersionRequest()) { + vcService.fetch(ctx.getTenantId()); + handleListVersions(ctx, msg.getListVersionRequest()); + } else if (msg.hasEntityContentRequest()) { + handleEntityContentRequest(ctx, msg.getEntityContentRequest()); + } else if (msg.hasEntitiesContentRequest()) { + handleEntitiesContentRequest(ctx, msg.getEntitiesContentRequest()); + } else if (msg.hasVersionsDiffRequest()) { + handleVersionsDiffRequest(ctx, msg.getVersionsDiffRequest()); + } + } + } + } catch (Exception e) { + reply(ctx, Optional.of(e)); + } finally { + lock.unlock(); + } + return null; + } + + private void handleEntitiesContentRequest(VersionControlRequestCtx ctx, EntitiesContentRequestMsg request) throws Exception { + var entityType = EntityType.valueOf(request.getEntityType()); + String path = getRelativePath(entityType, null); + var ids = vcService.listEntitiesAtVersion(ctx.getTenantId(), request.getVersionId(), path) + .stream().skip(request.getOffset()).limit(request.getLimit()).collect(Collectors.toList()); + if (!ids.isEmpty()) { + for (int i = 0; i < ids.size(); i++){ + VersionedEntityInfo info = ids.get(i); + var data = vcService.getFileContentAtCommit(ctx.getTenantId(), + getRelativePath(info.getExternalId().getEntityType(), info.getExternalId().getId().toString()), request.getVersionId()); + Iterable dataChunks = StringUtils.split(data, msgChunkSize); + int chunksCount = Iterables.size(dataChunks); + AtomicInteger chunkIndex = new AtomicInteger(); + int itemIdx = i; + dataChunks.forEach(chunk -> { + EntitiesContentResponseMsg.Builder response = EntitiesContentResponseMsg.newBuilder() + .setItemsCount(ids.size()) + .setItemIdx(itemIdx) + .setItem(EntityContentResponseMsg.newBuilder() + .setData(chunk) + .setChunksCount(chunksCount) + .setChunkIndex(chunkIndex.getAndIncrement()) + .build()); + reply(ctx, Optional.empty(), builder -> builder.setEntitiesContentResponse(response)); + }); + } + } else { + reply(ctx, Optional.empty(), builder -> builder.setEntitiesContentResponse( + EntitiesContentResponseMsg.newBuilder() + .setItemsCount(0))); + } + } + + private void handleEntityContentRequest(VersionControlRequestCtx ctx, EntityContentRequestMsg request) throws IOException { + String path = getRelativePath(EntityType.valueOf(request.getEntityType()), new UUID(request.getEntityIdMSB(), request.getEntityIdLSB()).toString()); + String data = vcService.getFileContentAtCommit(ctx.getTenantId(), path, request.getVersionId()); + + Iterable dataChunks = StringUtils.split(data, msgChunkSize); + String chunkedMsgId = UUID.randomUUID().toString(); + int chunksCount = Iterables.size(dataChunks); + + AtomicInteger chunkIndex = new AtomicInteger(); + dataChunks.forEach(chunk -> { + log.trace("[{}] sending chunk {} for 'getEntity'", chunkedMsgId, chunkIndex.get()); + reply(ctx, Optional.empty(), builder -> builder.setEntityContentResponse(EntityContentResponseMsg.newBuilder() + .setData(chunk).setChunksCount(chunksCount) + .setChunkIndex(chunkIndex.getAndIncrement()))); + }); + } + + private void handleListVersions(VersionControlRequestCtx ctx, ListVersionsRequestMsg request) throws Exception { + String path; + if (StringUtils.isNotEmpty(request.getEntityType())) { + var entityType = EntityType.valueOf(request.getEntityType()); + if (request.getEntityIdLSB() != 0 || request.getEntityIdMSB() != 0) { + path = getRelativePath(entityType, new UUID(request.getEntityIdMSB(), request.getEntityIdLSB()).toString()); + } else { + path = getRelativePath(entityType, null); + } + } else { + path = null; + } + SortOrder sortOrder = null; + if (StringUtils.isNotEmpty(request.getSortProperty())) { + var direction = SortOrder.Direction.DESC; + if (StringUtils.isNotEmpty(request.getSortDirection())) { + direction = SortOrder.Direction.valueOf(request.getSortDirection()); + } + sortOrder = new SortOrder(request.getSortProperty(), direction); + } + var data = vcService.listVersions(ctx.getTenantId(), request.getBranchName(), path, + new PageLink(request.getPageSize(), request.getPage(), request.getTextSearch(), sortOrder)); + reply(ctx, Optional.empty(), builder -> + builder.setListVersionsResponse(ListVersionsResponseMsg.newBuilder() + .setTotalPages(data.getTotalPages()) + .setTotalElements(data.getTotalElements()) + .setHasNext(data.hasNext()) + .addAllVersions(data.getData().stream().map( + v -> EntityVersionProto.newBuilder().setTs(v.getTimestamp()).setId(v.getId()).setName(v.getName()).setAuthor(v.getAuthor()).build() + ).collect(Collectors.toList()))) + ); + } + + private void handleListEntities(VersionControlRequestCtx ctx, ListEntitiesRequestMsg request) throws Exception { + EntityType entityType = StringUtils.isNotEmpty(request.getEntityType()) ? EntityType.valueOf(request.getEntityType()) : null; + var path = entityType != null ? getRelativePath(entityType, null) : null; + var data = vcService.listEntitiesAtVersion(ctx.getTenantId(), request.getVersionId(), path); + reply(ctx, Optional.empty(), builder -> + builder.setListEntitiesResponse(ListEntitiesResponseMsg.newBuilder() + .addAllEntities(data.stream().map(VersionedEntityInfo::getExternalId).map( + id -> VersionedEntityInfoProto.newBuilder() + .setEntityType(id.getEntityType().name()) + .setEntityIdMSB(id.getId().getMostSignificantBits()) + .setEntityIdLSB(id.getId().getLeastSignificantBits()).build() + ).collect(Collectors.toList())))); + } + + private void handleListBranches(VersionControlRequestCtx ctx, ListBranchesRequestMsg request) { + var branches = vcService.listBranches(ctx.getTenantId()).stream() + .map(branchInfo -> BranchInfoProto.newBuilder() + .setName(branchInfo.getName()) + .setIsDefault(branchInfo.isDefault()).build()).collect(Collectors.toList()); + reply(ctx, Optional.empty(), builder -> builder.setListBranchesResponse(ListBranchesResponseMsg.newBuilder().addAllBranches(branches))); + } + + private void handleVersionsDiffRequest(VersionControlRequestCtx ctx, TransportProtos.VersionsDiffRequestMsg request) throws IOException { + List diffList = vcService.getVersionsDiffList(ctx.getTenantId(), request.getPath(), request.getVersionId1(), request.getVersionId2()).stream() + .map(diff -> { + EntityId entityId = fromRelativePath(diff.getFilePath()); + return TransportProtos.EntityVersionsDiff.newBuilder() + .setEntityType(entityId.getEntityType().name()) + .setEntityIdMSB(entityId.getId().getMostSignificantBits()) + .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) + .setEntityDataAtVersion1(diff.getFileContentAtCommit1()) + .setEntityDataAtVersion2(diff.getFileContentAtCommit2()) + .setRawDiff(diff.getDiffStringValue()) + .build(); + }) + .collect(Collectors.toList()); + + reply(ctx, builder -> builder.setVersionsDiffResponse(TransportProtos.VersionsDiffResponseMsg.newBuilder() + .addAllDiff(diffList))); + } + + private void handleCommitRequest(VersionControlRequestCtx ctx, CommitRequestMsg request) throws Exception { + var tenantId = ctx.getTenantId(); + UUID txId = UUID.fromString(request.getTxId()); + if (request.hasPrepareMsg()) { + vcService.fetch(ctx.getTenantId()); + prepareCommit(ctx, txId, request.getPrepareMsg()); + } else if (request.hasAbortMsg()) { + PendingCommit current = pendingCommitMap.get(tenantId); + if (current != null && current.getTxId().equals(txId)) { + doAbortCurrentCommit(tenantId, current); + } + } else { + PendingCommit current = pendingCommitMap.get(tenantId); + if (current != null && current.getTxId().equals(txId)) { + try { + if (request.hasAddMsg()) { + addToCommit(ctx, current, request.getAddMsg()); + } else if (request.hasDeleteMsg()) { + deleteFromCommit(ctx, current, request.getDeleteMsg()); + } else if (request.hasPushMsg()) { + var result = vcService.push(current); + pendingCommitMap.remove(ctx.getTenantId()); + reply(ctx, result); + } + } catch (Exception e) { + doAbortCurrentCommit(tenantId, current, e); + throw e; + } + } else { + log.debug("[{}] Ignore request due to stale commit: {}", txId, request); + } + } + } + + private void prepareCommit(VersionControlRequestCtx ctx, UUID txId, PrepareMsg prepareMsg) { + var tenantId = ctx.getTenantId(); + var pendingCommit = new PendingCommit(tenantId, ctx.getNodeId(), txId, prepareMsg.getBranchName(), + prepareMsg.getCommitMsg(), prepareMsg.getAuthorName(), prepareMsg.getAuthorEmail()); + PendingCommit old = pendingCommitMap.get(tenantId); + if (old != null) { + doAbortCurrentCommit(tenantId, old); + } + pendingCommitMap.put(tenantId, pendingCommit); + vcService.prepareCommit(pendingCommit); + } + + private void deleteFromCommit(VersionControlRequestCtx ctx, PendingCommit commit, DeleteMsg deleteMsg) throws IOException { + vcService.deleteFolderContent(commit, deleteMsg.getRelativePath()); + } + + private void addToCommit(VersionControlRequestCtx ctx, PendingCommit commit, AddMsg addMsg) throws IOException { + log.trace("[{}] received chunk {} for 'addToCommit'", addMsg.getChunkedMsgId(), addMsg.getChunkIndex()); + Map chunkedMsgs = commit.getChunkedMsgs(); + String[] msgChunks = chunkedMsgs.computeIfAbsent(addMsg.getChunkedMsgId(), id -> new String[addMsg.getChunksCount()]); + msgChunks[addMsg.getChunkIndex()] = addMsg.getEntityDataJsonChunk(); + if (CollectionsUtil.countNonNull(msgChunks) == msgChunks.length) { + log.trace("[{}] collected all chunks for 'addToCommit'", addMsg.getChunkedMsgId()); + String entityDataJson = String.join("", msgChunks); + chunkedMsgs.remove(addMsg.getChunkedMsgId()); + vcService.add(commit, addMsg.getRelativePath(), entityDataJson); + } + } + + private void doAbortCurrentCommit(TenantId tenantId, PendingCommit current) { + doAbortCurrentCommit(tenantId, current, null); + } + + private void doAbortCurrentCommit(TenantId tenantId, PendingCommit current, Exception e) { + vcService.abort(current); + pendingCommitMap.remove(tenantId); + //TODO: push notification to core using old.getNodeId() to cancel old commit processing on the caller side. + } + + private void handleClearRepositoryCommand(VersionControlRequestCtx ctx) { + try { + vcService.clearRepository(ctx.getTenantId()); + reply(ctx, Optional.empty()); + } catch (Exception e) { + log.debug("[{}] Failed to connect to the repository: ", ctx, e); + reply(ctx, Optional.of(e)); + } + } + + private void handleInitRepositoryCommand(VersionControlRequestCtx ctx) { + try { + vcService.initRepository(ctx.getTenantId(), ctx.getSettings()); + reply(ctx, Optional.empty()); + } catch (Exception e) { + log.debug("[{}] Failed to connect to the repository: ", ctx, e); + reply(ctx, Optional.of(e)); + } + } + + + private void handleTestRepositoryCommand(VersionControlRequestCtx ctx) { + try { + vcService.testRepository(ctx.getTenantId(), ctx.getSettings()); + reply(ctx, Optional.empty()); + } catch (Exception e) { + log.debug("[{}] Failed to connect to the repository: ", ctx, e); + reply(ctx, Optional.of(e)); + } + } + + private void reply(VersionControlRequestCtx ctx, VersionCreationResult result) { + var responseBuilder = CommitResponseMsg.newBuilder().setAdded(result.getAdded()) + .setModified(result.getModified()) + .setRemoved(result.getRemoved()); + + if (result.getVersion() != null) { + responseBuilder.setTs(result.getVersion().getTimestamp()) + .setCommitId(result.getVersion().getId()) + .setName(result.getVersion().getName()) + .setAuthor(result.getVersion().getAuthor()); + } + + reply(ctx, Optional.empty(), builder -> builder.setCommitResponse(responseBuilder)); + } + + private void reply(VersionControlRequestCtx ctx, Optional e) { + reply(ctx, e, null); + } + + private void reply(VersionControlRequestCtx ctx, Function enrichFunction) { + reply(ctx, Optional.empty(), enrichFunction); + } + + private void reply(VersionControlRequestCtx ctx, Optional e, Function enrichFunction) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, ctx.getNodeId()); + VersionControlResponseMsg.Builder builder = VersionControlResponseMsg.newBuilder() + .setRequestIdMSB(ctx.getRequestId().getMostSignificantBits()) + .setRequestIdLSB(ctx.getRequestId().getLeastSignificantBits()); + if (e.isPresent()) { + log.debug("[{}][{}] Failed to process task", ctx.getTenantId(), ctx.getRequestId(), e.get()); + var message = e.get().getMessage(); + builder.setError(message != null ? message : e.get().getClass().getSimpleName()); + } else { + if (enrichFunction != null) { + builder = enrichFunction.apply(builder); + } else { + builder.setGenericResponse(TransportProtos.GenericRepositoryResponseMsg.newBuilder().build()); + } + log.debug("[{}][{}] Processed task", ctx.getTenantId(), ctx.getRequestId()); + } + + ToCoreNotificationMsg msg = ToCoreNotificationMsg.newBuilder().setVcResponseMsg(builder).build(); + log.trace("[{}][{}] PUSHING reply: {} to: {}", ctx.getTenantId(), ctx.getRequestId(), msg, tpi); + producer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); + } + + private RepositorySettings getEntitiesVersionControlSettings(ToVersionControlServiceMsg msg) { + Optional settingsOpt = encodingService.decode(msg.getVcSettings().toByteArray()); + if (settingsOpt.isPresent()) { + return settingsOpt.get(); + } else { + log.warn("Failed to parse VC settings: {}", msg.getVcSettings()); + throw new RuntimeException("Failed to parse vc settings!"); + } + } + + private String getRelativePath(EntityType entityType, String entityId) { + String path = entityType.name().toLowerCase(); + if (entityId != null) { + path += "/" + entityId + ".json"; + } + return path; + } + + private Lock getRepoLock(TenantId tenantId) { + return tenantRepoLocks.computeIfAbsent(tenantId, t -> new ReentrantLock(true)); + } + + private void logTaskExecution(VersionControlRequestCtx ctx, ListenableFuture future, long startTs) { + if (log.isTraceEnabled()) { + Futures.addCallback(future, new FutureCallback() { + + @Override + public void onSuccess(@Nullable Object result) { + log.trace("[{}][{}] Task processing took: {}ms", ctx.getTenantId(), ctx.getRequestId(), (System.currentTimeMillis() - startTs)); + } + + @Override + public void onFailure(Throwable t) { + log.trace("[{}][{}] Task failed: ", ctx.getTenantId(), ctx.getRequestId(), t); + } + }, MoreExecutors.directExecutor()); + } + } +} diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java new file mode 100644 index 0000000000..d3615f7228 --- /dev/null +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java @@ -0,0 +1,276 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.service.sync.vc.GitRepository.Diff; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +@Slf4j +@ConditionalOnProperty(prefix = "vc", value = "git.service", havingValue = "local", matchIfMissing = true) +@Service +public class DefaultGitRepositoryService implements GitRepositoryService { + + @Value("${java.io.tmpdir}/repositories") + private String defaultFolder; + + @Value("${vc.git.repositories-folder:${java.io.tmpdir}/repositories}") + private String repositoriesFolder; + + private final Map repositories = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + if (StringUtils.isEmpty(repositoriesFolder)) { + repositoriesFolder = defaultFolder; + } + } + + @Override + public Set getActiveRepositoryTenants() { + return new HashSet<>(repositories.keySet()); + } + + @Override + public void prepareCommit(PendingCommit commit) { + GitRepository repository = checkRepository(commit.getTenantId()); + String branch = commit.getBranch(); + try { + repository.fetch(); + + repository.createAndCheckoutOrphanBranch(commit.getWorkingBranch()); + repository.resetAndClean(); + + if (repository.listRemoteBranches().contains(new BranchInfo(branch, false))) { + repository.merge(branch); + } + } catch (IOException | GitAPIException gitAPIException) { + //TODO: analyze and return meaningful exceptions that we can show to the client; + throw new RuntimeException(gitAPIException); + } + } + + @Override + public void deleteFolderContent(PendingCommit commit, String relativePath) throws IOException { + GitRepository repository = checkRepository(commit.getTenantId()); + FileUtils.deleteDirectory(Path.of(repository.getDirectory(), relativePath).toFile()); + } + + @Override + public void add(PendingCommit commit, String relativePath, String entityDataJson) throws IOException { + GitRepository repository = checkRepository(commit.getTenantId()); + FileUtils.write(Path.of(repository.getDirectory(), relativePath).toFile(), entityDataJson, StandardCharsets.UTF_8); + } + + @Override + public VersionCreationResult push(PendingCommit commit) { + GitRepository repository = checkRepository(commit.getTenantId()); + try { + repository.add("."); + + VersionCreationResult result = new VersionCreationResult(); + GitRepository.Status status = repository.status(); + result.setAdded(status.getAdded().size()); + result.setModified(status.getModified().size()); + result.setRemoved(status.getRemoved().size()); + + if (result.getAdded() > 0 || result.getModified() > 0 || result.getRemoved() > 0) { + GitRepository.Commit gitCommit = repository.commit(commit.getVersionName(), commit.getAuthorName(), commit.getAuthorEmail()); + repository.push(commit.getWorkingBranch(), commit.getBranch()); + result.setVersion(toVersion(gitCommit)); + } + return result; + } catch (GitAPIException gitAPIException) { + //TODO: analyze and return meaningful exceptions that we can show to the client; + throw new RuntimeException(gitAPIException); + } finally { + cleanUp(commit); + } + } + + @SneakyThrows + @Override + public void cleanUp(PendingCommit commit) { + log.debug("[{}] Cleanup tenant repository started.", commit.getTenantId()); + GitRepository repository = checkRepository(commit.getTenantId()); + try { + repository.createAndCheckoutOrphanBranch(EntityId.NULL_UUID.toString()); + } catch (Exception e) { + if (!e.getMessage().contains("NO_CHANGE")) { + throw e; + } + } + repository.resetAndClean(); + repository.deleteLocalBranchIfExists(commit.getWorkingBranch()); + log.debug("[{}] Cleanup tenant repository completed.", commit.getTenantId()); + } + + @Override + public void abort(PendingCommit commit) { + cleanUp(commit); + } + + @Override + public void fetch(TenantId tenantId) throws GitAPIException { + var repository = repositories.get(tenantId); + if (repository != null) { + log.debug("[{}] Fetching tenant repository.", tenantId); + repository.fetch(); + log.debug("[{}] Fetched tenant repository.", tenantId); + } + } + + @Override + public String getFileContentAtCommit(TenantId tenantId, String relativePath, String versionId) throws IOException { + GitRepository repository = checkRepository(tenantId); + return repository.getFileContentAtCommit(relativePath, versionId); + } + + @Override + public List getVersionsDiffList(TenantId tenantId, String path, String versionId1, String versionId2) throws IOException { + GitRepository repository = checkRepository(tenantId); + return repository.getDiffList(versionId1, versionId2, path); + } + + @Override + public String getContentsDiff(TenantId tenantId, String content1, String content2) throws IOException { + GitRepository repository = checkRepository(tenantId); + return repository.getContentsDiff(content1, content2); + } + + @Override + public List listBranches(TenantId tenantId) { + GitRepository repository = checkRepository(tenantId); + try { + return repository.listRemoteBranches(); + } catch (GitAPIException gitAPIException) { + //TODO: analyze and return meaningful exceptions that we can show to the client; + throw new RuntimeException(gitAPIException); + } + } + + private GitRepository checkRepository(TenantId tenantId) { + return Optional.ofNullable(repositories.get(tenantId)) + .orElseThrow(() -> new IllegalStateException("Repository is not initialized")); + } + + @Override + public PageData listVersions(TenantId tenantId, String branch, String path, PageLink pageLink) throws Exception { + GitRepository repository = checkRepository(tenantId); + return repository.listCommits(branch, path, pageLink).mapData(this::toVersion); + } + + @Override + public List listEntitiesAtVersion(TenantId tenantId, String versionId, String path) throws Exception { + GitRepository repository = checkRepository(tenantId); + return repository.listFilesAtCommit(versionId, path).stream() + .map(filePath -> { + EntityId entityId = fromRelativePath(filePath); + VersionedEntityInfo info = new VersionedEntityInfo(); + info.setExternalId(entityId); + return info; + }) + .collect(Collectors.toList()); + } + + @Override + public void testRepository(TenantId tenantId, RepositorySettings settings) throws Exception { + Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); + GitRepository.test(settings, repositoryDirectory.toFile()); + } + + @Override + public void initRepository(TenantId tenantId, RepositorySettings settings) throws Exception { + clearRepository(tenantId); + log.debug("[{}] Init tenant repository started.", tenantId); + Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); + GitRepository repository; + if (Files.exists(repositoryDirectory)) { + FileUtils.forceDelete(repositoryDirectory.toFile()); + } + + Files.createDirectories(repositoryDirectory); + repository = GitRepository.clone(settings, repositoryDirectory.toFile()); + repositories.put(tenantId, repository); + log.debug("[{}] Init tenant repository completed.", tenantId); + } + + @Override + public RepositorySettings getRepositorySettings(TenantId tenantId) throws Exception { + var gitRepository = repositories.get(tenantId); + return gitRepository != null ? gitRepository.getSettings() : null; + } + + @Override + public void clearRepository(TenantId tenantId) throws IOException { + GitRepository repository = repositories.get(tenantId); + if (repository != null) { + log.debug("[{}] Clear tenant repository started.", tenantId); + FileUtils.deleteDirectory(new File(repository.getDirectory())); + repositories.remove(tenantId); + log.debug("[{}] Clear tenant repository completed.", tenantId); + } + } + + private EntityVersion toVersion(GitRepository.Commit commit) { + return new EntityVersion(commit.getTimestamp(), commit.getId(), commit.getMessage(), this.getAuthor(commit)); + } + + private String getAuthor(GitRepository.Commit commit) { + String author = String.format("<%s>", commit.getAuthorEmail()); + if (StringUtils.isNotBlank(commit.getAuthorName())) { + author = String.format("%s %s", commit.getAuthorName(), author); + } + return author; + } + + public static EntityId fromRelativePath(String path) { + EntityType entityType = EntityType.valueOf(StringUtils.substringBefore(path, "/").toUpperCase()); + String entityId = StringUtils.substringBetween(path, "/", ".json"); + return EntityIdFactory.getByTypeAndUuid(entityType, entityId); + } +} diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java new file mode 100644 index 0000000000..7d1710a87b --- /dev/null +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java @@ -0,0 +1,517 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Ordering; +import com.google.common.collect.Streams; +import lombok.Data; +import lombok.Getter; +import org.apache.commons.lang3.StringUtils; +import org.apache.sshd.common.util.security.SecurityUtils; +import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.GitCommand; +import org.eclipse.jgit.api.ListBranchCommand; +import org.eclipse.jgit.api.LogCommand; +import org.eclipse.jgit.api.LsRemoteCommand; +import org.eclipse.jgit.api.ResetCommand; +import org.eclipse.jgit.api.TransportCommand; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.diff.DiffEntry; +import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.diff.EditList; +import org.eclipse.jgit.diff.HistogramDiff; +import org.eclipse.jgit.diff.RawText; +import org.eclipse.jgit.diff.RawTextComparator; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; +import org.eclipse.jgit.lib.ObjectReader; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.RevFilter; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.FetchResult; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.SshTransport; +import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; +import org.eclipse.jgit.transport.sshd.JGitKeyCache; +import org.eclipse.jgit.transport.sshd.ServerKeyDatabase; +import org.eclipse.jgit.transport.sshd.SshdSessionFactory; +import org.eclipse.jgit.transport.sshd.SshdSessionFactoryBuilder; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilter; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.security.KeyPair; +import java.security.PublicKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class GitRepository { + + private final Git git; + @Getter + private final RepositorySettings settings; + private final CredentialsProvider credentialsProvider; + private final SshdSessionFactory sshSessionFactory; + + @Getter + private final String directory; + + private ObjectId headId; + + private GitRepository(Git git, RepositorySettings settings, CredentialsProvider credentialsProvider, SshdSessionFactory sshSessionFactory, String directory) { + this.git = git; + this.settings = settings; + this.credentialsProvider = credentialsProvider; + this.sshSessionFactory = sshSessionFactory; + this.directory = directory; + } + + public static GitRepository clone(RepositorySettings settings, File directory) throws GitAPIException { + CredentialsProvider credentialsProvider = null; + SshdSessionFactory sshSessionFactory = null; + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { + credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { + sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); + } + CloneCommand cloneCommand = Git.cloneRepository() + .setURI(settings.getRepositoryUri()) + .setDirectory(directory) + .setNoCheckout(true); + configureTransportCommand(cloneCommand, credentialsProvider, sshSessionFactory); + Git git = cloneCommand.call(); + return new GitRepository(git, settings, credentialsProvider, sshSessionFactory, directory.getAbsolutePath()); + } + + public static GitRepository open(File directory, RepositorySettings settings) throws IOException { + Git git = Git.open(directory); + CredentialsProvider credentialsProvider = null; + SshdSessionFactory sshSessionFactory = null; + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { + credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { + sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); + } + return new GitRepository(git, settings, credentialsProvider, sshSessionFactory, directory.getAbsolutePath()); + } + + public static void test(RepositorySettings settings, File directory) throws GitAPIException { + CredentialsProvider credentialsProvider = null; + SshdSessionFactory sshSessionFactory = null; + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { + credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { + sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); + } + LsRemoteCommand lsRemoteCommand = Git.lsRemoteRepository().setRemote(settings.getRepositoryUri()); + configureTransportCommand(lsRemoteCommand, credentialsProvider, sshSessionFactory); + lsRemoteCommand.call(); + } + + public void fetch() throws GitAPIException { + FetchResult result = execute(git.fetch() + .setRemoveDeletedRefs(true)); + Ref head = result.getAdvertisedRef(Constants.HEAD); + if (head != null) { + this.headId = head.getObjectId(); + } + } + + public void deleteLocalBranchIfExists(String branch) throws GitAPIException { + execute(git.branchDelete() + .setBranchNames(branch) + .setForce(true)); + } + + public void resetAndClean() throws GitAPIException { + execute(git.reset() + .setMode(ResetCommand.ResetType.HARD)); + execute(git.clean() + .setForce(true) + .setCleanDirectories(true)); + } + + public void merge(String branch) throws IOException, GitAPIException { + ObjectId branchId = resolve("origin/" + branch); + if (branchId == null) { + throw new IllegalArgumentException("Branch not found"); + } + execute(git.merge() + .include(branchId)); + } + + public List listRemoteBranches() throws GitAPIException { + return execute(git.branchList() + .setListMode(ListBranchCommand.ListMode.REMOTE)).stream() + .filter(ref -> !ref.getName().equals(Constants.HEAD)) + .map(this::toBranchInfo) + .distinct().collect(Collectors.toList()); + } + + public PageData listCommits(String branch, PageLink pageLink) throws IOException, GitAPIException { + return listCommits(branch, null, pageLink); + } + + public PageData listCommits(String branch, String path, PageLink pageLink) throws IOException, GitAPIException { + ObjectId branchId = resolve("origin/" + branch); + if (branchId == null) { + return new PageData<>(); + } + LogCommand command = git.log() + .add(branchId); + + if (StringUtils.isNotEmpty(pageLink.getTextSearch())) { + command.setRevFilter(new NoMergesAndCommitMessageFilter(pageLink.getTextSearch())); + } else { + command.setRevFilter(RevFilter.NO_MERGES); + } + + if (StringUtils.isNotEmpty(path)) { + command.addPath(path); + } + + Iterable commits = execute(command); + return iterableToPageData(commits, this::toCommit, pageLink, revCommitComparatorFunction); + } + + public List listFilesAtCommit(String commitId) throws IOException { + return listFilesAtCommit(commitId, null); + } + + public List listFilesAtCommit(String commitId, String path) throws IOException { + List files = new ArrayList<>(); + RevCommit revCommit = resolveCommit(commitId); + try (TreeWalk treeWalk = new TreeWalk(git.getRepository())) { + treeWalk.reset(revCommit.getTree().getId()); + if (StringUtils.isNotEmpty(path)) { + treeWalk.setFilter(PathFilter.create(path)); + } + treeWalk.setRecursive(true); + while (treeWalk.next()) { + files.add(treeWalk.getPathString()); + } + } + return files; + } + + + public String getFileContentAtCommit(String file, String commitId) throws IOException { + RevCommit revCommit = resolveCommit(commitId); + try (TreeWalk treeWalk = TreeWalk.forPath(git.getRepository(), file, revCommit.getTree())) { + if (treeWalk == null) { + throw new IllegalArgumentException("File not found"); + } + ObjectId blobId = treeWalk.getObjectId(0); + try (ObjectReader objectReader = git.getRepository().newObjectReader()) { + ObjectLoader objectLoader = objectReader.open(blobId); + byte[] bytes = objectLoader.getBytes(); + return new String(bytes, StandardCharsets.UTF_8); + } + } + } + + + public void createAndCheckoutOrphanBranch(String name) throws GitAPIException { + execute(git.checkout() + .setOrphan(true) + .setForced(true) + .setName(name)); + } + + public void add(String filesPattern) throws GitAPIException { + execute(git.add().setUpdate(true).addFilepattern(filesPattern)); + execute(git.add().addFilepattern(filesPattern)); + } + + public Status status() throws GitAPIException { + org.eclipse.jgit.api.Status status = execute(git.status()); + Set modified = new HashSet<>(); + modified.addAll(status.getModified()); + modified.addAll(status.getChanged()); + return new Status(status.getAdded(), modified, status.getRemoved()); + } + + public Commit commit(String message, String authorName, String authorEmail) throws GitAPIException { + RevCommit revCommit = execute(git.commit() + .setAuthor(authorName, authorEmail) + .setMessage(message)); + return toCommit(revCommit); + } + + + public void push(String localBranch, String remoteBranch) throws GitAPIException { + execute(git.push() + .setRefSpecs(new RefSpec(localBranch + ":" + remoteBranch))); + } + + public String getContentsDiff(String content1, String content2) throws IOException { + RawText rawContent1 = new RawText(content1.getBytes()); + RawText rawContent2 = new RawText(content2.getBytes()); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DiffFormatter diffFormatter = new DiffFormatter(out); + diffFormatter.setRepository(git.getRepository()); + + EditList edits = new EditList(); + edits.addAll(new HistogramDiff().diff(RawTextComparator.DEFAULT, rawContent1, rawContent2)); + diffFormatter.format(edits, rawContent1, rawContent2); + return out.toString(); + } + + public List getDiffList(String commit1, String commit2, String path) throws IOException { + ObjectReader reader = git.getRepository().newObjectReader(); + + CanonicalTreeParser tree1Iter = new CanonicalTreeParser(); + ObjectId tree1 = resolveCommit(commit1).getTree(); + tree1Iter.reset(reader, tree1); + + CanonicalTreeParser tree2Iter = new CanonicalTreeParser(); + ObjectId tree2 = resolveCommit(commit2).getTree(); + tree2Iter.reset(reader, tree2); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + DiffFormatter diffFormatter = new DiffFormatter(out); + diffFormatter.setRepository(git.getRepository()); + if (StringUtils.isNotEmpty(path)) { + diffFormatter.setPathFilter(PathFilter.create(path)); + } + + return diffFormatter.scan(tree1, tree2).stream() + .map(diffEntry -> { + Diff diff = new Diff(); + try { + out.reset(); + diffFormatter.format(diffEntry); + diff.setDiffStringValue(out.toString()); + diff.setFilePath(diffEntry.getChangeType() != DiffEntry.ChangeType.DELETE ? diffEntry.getNewPath() : diffEntry.getOldPath()); + diff.setChangeType(diffEntry.getChangeType()); + try { + diff.setFileContentAtCommit1(getFileContentAtCommit(diff.getFilePath(), commit1)); + } catch (IllegalArgumentException ignored) { + } + try { + diff.setFileContentAtCommit2(getFileContentAtCommit(diff.getFilePath(), commit2)); + } catch (IllegalArgumentException ignored) { + } + return diff; + } catch (Exception e) { + throw new RuntimeException(e); + } + }) + .collect(Collectors.toList()); + } + + private BranchInfo toBranchInfo(Ref ref) { + String name = org.eclipse.jgit.lib.Repository.shortenRefName(ref.getName()); + String branchName = StringUtils.removeStart(name, "origin/"); + boolean isDefault = this.headId != null && this.headId.equals(ref.getObjectId()); + return new BranchInfo(branchName, isDefault); + } + + private Commit toCommit(RevCommit revCommit) { + return new Commit(revCommit.getCommitTime() * 1000l, revCommit.getName(), + revCommit.getFullMessage(), revCommit.getAuthorIdent().getName(), revCommit.getAuthorIdent().getEmailAddress()); + } + + private RevCommit resolveCommit(String id) throws IOException { + return git.getRepository().parseCommit(resolve(id)); + } + + private ObjectId resolve(String rev) throws IOException { + ObjectId result = git.getRepository().resolve(rev); + if (result == null) { + throw new IllegalArgumentException("Failed to parse git revision string: \"" + rev + "\""); + } + return result; + } + + private , T> T execute(C command) throws GitAPIException { + if (command instanceof TransportCommand) { + configureTransportCommand((TransportCommand) command, credentialsProvider, sshSessionFactory); + } + return command.call(); + } + + private static Function> revCommitComparatorFunction = pageLink -> { + SortOrder sortOrder = pageLink.getSortOrder(); + if (sortOrder != null + && sortOrder.getProperty().equals("timestamp") + && SortOrder.Direction.ASC.equals(sortOrder.getDirection())) { + return Comparator.comparingInt(RevCommit::getCommitTime); + } + return null; + }; + + private static PageData iterableToPageData(Iterable iterable, + Function mapper, + PageLink pageLink, + Function> comparatorFunction) { + iterable = Streams.stream(iterable).collect(Collectors.toList()); + int totalElements = Iterables.size(iterable); + int totalPages = pageLink.getPageSize() > 0 ? (int) Math.ceil((float) totalElements / pageLink.getPageSize()) : 1; + int startIndex = pageLink.getPageSize() * pageLink.getPage(); + int limit = startIndex + pageLink.getPageSize(); + if (comparatorFunction != null) { + Comparator comparator = comparatorFunction.apply(pageLink); + if (comparator != null) { + iterable = Ordering.from(comparator).immutableSortedCopy(iterable); + } + } + iterable = Iterables.limit(iterable, limit); + if (startIndex < totalElements) { + iterable = Iterables.skip(iterable, startIndex); + } else { + iterable = Collections.emptyList(); + } + List data = Streams.stream(iterable).map(mapper) + .collect(Collectors.toList()); + boolean hasNext = pageLink.getPageSize() > 0 && totalElements > startIndex + data.size(); + return new PageData<>(data, totalPages, totalElements, hasNext); + } + + private static void configureTransportCommand(TransportCommand transportCommand, CredentialsProvider credentialsProvider, SshdSessionFactory sshSessionFactory) { + if (credentialsProvider != null) { + transportCommand.setCredentialsProvider(credentialsProvider); + } + if (sshSessionFactory != null) { + transportCommand.setTransportConfigCallback(transport -> { + if (transport instanceof SshTransport) { + SshTransport sshTransport = (SshTransport) transport; + sshTransport.setSshSessionFactory(sshSessionFactory); + } + }); + } + } + + private static CredentialsProvider newCredentialsProvider(String username, String password) { + return new UsernamePasswordCredentialsProvider(username, password == null ? "" : password); + } + + private static SshdSessionFactory newSshdSessionFactory(String privateKey, String password, File directory) { + SshdSessionFactory sshSessionFactory = null; + if (StringUtils.isNotBlank(privateKey)) { + Iterable keyPairs = loadKeyPairs(privateKey, password); + sshSessionFactory = new SshdSessionFactoryBuilder() + .setPreferredAuthentications("publickey") + .setDefaultKeysProvider(file -> keyPairs) + .setHomeDirectory(directory) + .setSshDirectory(directory) + .setServerKeyDatabase((file, file2) -> new ServerKeyDatabase() { + @Override + public List lookup(String connectAddress, InetSocketAddress remoteAddress, Configuration config) { + return Collections.emptyList(); + } + + @Override + public boolean accept(String connectAddress, InetSocketAddress remoteAddress, PublicKey serverKey, Configuration config, CredentialsProvider provider) { + return true; + } + }) + .build(new JGitKeyCache()); + } + return sshSessionFactory; + } + + private static Iterable loadKeyPairs(String privateKeyContent, String password) { + Iterable keyPairs = null; + try { + keyPairs = SecurityUtils.loadKeyPairIdentities(null, + null, new ByteArrayInputStream(privateKeyContent.getBytes()), (session, resourceKey, retryIndex) -> password); + } catch (Exception e) {} + if (keyPairs == null) { + throw new IllegalArgumentException("Failed to load ssh private key"); + } + return keyPairs; + } + + private static class NoMergesAndCommitMessageFilter extends RevFilter { + + private final String textSearch; + + NoMergesAndCommitMessageFilter(String textSearch) { + this.textSearch = textSearch.toLowerCase(); + } + + @Override + public boolean include(RevWalk walker, RevCommit c) { + return c.getParentCount() < 2 && c.getFullMessage().toLowerCase().contains(this.textSearch); + } + + @Override + public RevFilter clone() { + return this; + } + + @Override + public boolean requiresCommitBody() { + return false; + } + + @Override + public String toString() { + return "NO_MERGES_AND_COMMIT_MESSAGE"; + } + } + + @Data + public static class Commit { + private final long timestamp; + private final String id; + private final String message; + private final String authorName; + private final String authorEmail; + } + + @Data + public static class Status { + private final Set added; + private final Set modified; + private final Set removed; + } + + @Data + public static class Diff { + private String filePath; + private DiffEntry.ChangeType changeType; + private String fileContentAtCommit1; + private String fileContentAtCommit2; + private String diffStringValue; + } + +} diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java new file mode 100644 index 0000000000..d4f909df9e --- /dev/null +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepositoryService.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.sync.vc.BranchInfo; +import org.thingsboard.server.common.data.sync.vc.EntityVersion; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.VersionCreationResult; +import org.thingsboard.server.common.data.sync.vc.VersionedEntityInfo; +import org.thingsboard.server.service.sync.vc.GitRepository.Diff; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +public interface GitRepositoryService { + + Set getActiveRepositoryTenants(); + + void prepareCommit(PendingCommit pendingCommit); + + PageData listVersions(TenantId tenantId, String branch, String path, PageLink pageLink) throws Exception; + + List listEntitiesAtVersion(TenantId tenantId, String versionId, String path) throws Exception; + + void testRepository(TenantId tenantId, RepositorySettings settings) throws Exception; + + void initRepository(TenantId tenantId, RepositorySettings settings) throws Exception; + + RepositorySettings getRepositorySettings(TenantId tenantId) throws Exception; + + void clearRepository(TenantId tenantId) throws IOException; + + void add(PendingCommit commit, String relativePath, String entityDataJson) throws IOException; + + void deleteFolderContent(PendingCommit commit, String relativePath) throws IOException; + + VersionCreationResult push(PendingCommit commit); + + void cleanUp(PendingCommit commit); + + void abort(PendingCommit commit); + + List listBranches(TenantId tenantId); + + String getFileContentAtCommit(TenantId tenantId, String relativePath, String versionId) throws IOException; + + List getVersionsDiffList(TenantId tenantId, String path, String versionId1, String versionId2) throws IOException; + + String getContentsDiff(TenantId tenantId, String content1, String content2) throws IOException; + + void fetch(TenantId tenantId) throws GitAPIException; +} diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java new file mode 100644 index 0000000000..20b1991c9f --- /dev/null +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/PendingCommit.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import lombok.Data; +import org.thingsboard.server.common.data.id.TenantId; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +@Data +public class PendingCommit { + + private final UUID txId; + private final String nodeId; + private final TenantId tenantId; + private final String workingBranch; + private String branch; + private String versionName; + + private String authorName; + private String authorEmail; + + private Map chunkedMsgs; + + public PendingCommit(TenantId tenantId, String nodeId, UUID txId, String branch, String versionName, String authorName, String authorEmail) { + this.tenantId = tenantId; + this.nodeId = nodeId; + this.txId = txId; + this.branch = branch; + this.versionName = versionName; + this.authorName = authorName; + this.authorEmail = authorEmail; + this.workingBranch = txId.toString(); + } + + public Map getChunkedMsgs() { + if (chunkedMsgs == null) { + chunkedMsgs = new ConcurrentHashMap<>(); + } + return chunkedMsgs; + } + +} diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java new file mode 100644 index 0000000000..26ab0d94c1 --- /dev/null +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/VersionControlRequestCtx.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.sync.vc; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg; + +import java.util.UUID; + +@RequiredArgsConstructor +@Data +public class VersionControlRequestCtx { + private final String nodeId; + private final UUID requestId; + private final TenantId tenantId; + private final RepositorySettings settings; + + public VersionControlRequestCtx(ToVersionControlServiceMsg msg, RepositorySettings settings) { + this.nodeId = msg.getNodeId(); + this.requestId = new UUID(msg.getRequestIdMSB(), msg.getRequestIdLSB()); + this.tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); + this.settings = settings; + } + + @Override + public String toString() { + return "VersionControlRequestCtx{" + + "nodeId='" + nodeId + '\'' + + ", requestId=" + requestId + + ", tenantId=" + tenantId + + '}'; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/Dao.java b/dao/src/main/java/org/thingsboard/server/dao/Dao.java index 932a6e5ec1..663c19348d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/Dao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/Dao.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao; import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; import java.util.Collection; @@ -36,8 +37,12 @@ public interface Dao { T save(TenantId tenantId, T t); + T saveAndFlush(TenantId tenantId, T t); + boolean removeById(TenantId tenantId, UUID id); void removeAllByIds(Collection ids); + default EntityType getEntityType() { return null; } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java index 4f250c47c8..4be3f8e6ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java @@ -31,6 +31,8 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.UUID; +import java.util.function.Consumer; +import java.util.function.Function; public abstract class DaoUtil { @@ -50,7 +52,7 @@ public abstract class DaoUtil { return toPageable(pageLink, Collections.emptyMap()); } - public static Pageable toPageable(PageLink pageLink, Map columnMap) { + public static Pageable toPageable(PageLink pageLink, Map columnMap) { return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), pageLink.toSort(pageLink.getSortOrder(), columnMap)); } @@ -58,7 +60,7 @@ public abstract class DaoUtil { return toPageable(pageLink, Collections.emptyMap(), sortOrders); } - public static Pageable toPageable(PageLink pageLink, Map columnMap, List sortOrders) { + public static Pageable toPageable(PageLink pageLink, Map columnMap, List sortOrders) { return PageRequest.of(pageLink.getPage(), pageLink.getPageSize(), pageLink.toSort(sortOrders, columnMap)); } @@ -107,4 +109,18 @@ public abstract class DaoUtil { return ids; } + public static void processInBatches(Function> finder, int batchSize, Consumer processor) { + PageLink pageLink = new PageLink(batchSize); + PageData batch; + + boolean hasNextBatch; + do { + batch = finder.apply(pageLink); + batch.getData().forEach(processor); + + hasNextBatch = batch.hasNext(); + pageLink = pageLink.nextPageLink(); + } while (hasNextBatch); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ExportableEntityDao.java b/dao/src/main/java/org/thingsboard/server/dao/ExportableEntityDao.java new file mode 100644 index 0000000000..77800c31e8 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/ExportableEntityDao.java @@ -0,0 +1,35 @@ +/** + * 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; + +import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; + +import java.util.UUID; + +public interface ExportableEntityDao> extends Dao { + + T findByTenantIdAndExternalId(UUID tenantId, UUID externalId); + + default T findByTenantIdAndName(UUID tenantId, String name) { throw new UnsupportedOperationException(); } + + PageData findByTenantId(UUID tenantId, PageLink pageLink); + + I getExternalIdByInternal(I internalId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/ExportableEntityRepository.java b/dao/src/main/java/org/thingsboard/server/dao/ExportableEntityRepository.java new file mode 100644 index 0000000000..6e2d2e115a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/ExportableEntityRepository.java @@ -0,0 +1,24 @@ +/** + * 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; + +import java.util.UUID; + +public interface ExportableEntityRepository { + + D findByTenantIdAndExternalId(UUID tenantId, UUID externalId); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 0432e69bca..e3c51b48a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -19,10 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; +import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; import java.util.List; @@ -33,7 +35,7 @@ import java.util.UUID; * The Interface AssetDao. * */ -public interface AssetDao extends Dao, TenantEntityDao { +public interface AssetDao extends Dao, TenantEntityDao, ExportableEntityDao { /** * Find asset info by id. diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java index 9a8526f3cf..4e16f40c5b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetRedisCache.java @@ -23,13 +23,13 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.cache.TbFSTRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("AssetCache") public class AssetRedisCache extends RedisTbTransactionalCache { public AssetRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ASSET_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.ASSET_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index 729c923e54..31c40e0d96 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -22,9 +22,8 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -53,7 +52,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.dao.DaoUtil.toUUIDs; @@ -125,16 +123,14 @@ public class BaseAssetService extends AbstractCachedEntityService entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(asset.getTenantId(), assetId).get(); - if (entityViews != null && !entityViews.isEmpty()) { - throw new DataValidationException("Can't delete asset that has entity views!"); - } - } catch (ExecutionException | InterruptedException e) { - log.error("Exception while finding entity views for assetId [{}]", assetId, e); - throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e); + List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(asset.getTenantId(), assetId); + if (entityViews != null && !entityViews.isEmpty()) { + throw new DataValidationException("Can't delete asset that has entity views!"); } publishEvictEvent(new AssetCacheEvictEvent(asset.getTenantId(), asset.getName(), null)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java index 3dc3f1e232..68a2a11c4a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeCacheKey.java @@ -34,6 +34,6 @@ public class AttributeCacheKey implements Serializable { @Override public String toString() { - return entityId + "_" + scope + "_" + key; + return "{" + entityId + "}" + scope + "_" + key; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeRedisCache.java index 80c52105fd..dda00543a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/AttributeRedisCache.java @@ -15,21 +15,99 @@ */ package org.thingsboard.server.dao.attributes; +import com.google.protobuf.InvalidProtocolBufferException; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.serializer.SerializationException; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeValueProto; +import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("AttributeCache") public class AttributeRedisCache extends RedisTbTransactionalCache { public AttributeRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ATTRIBUTES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.ATTRIBUTES_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>() { + @Override + public byte[] serialize(AttributeKvEntry attributeKvEntry) throws SerializationException { + AttributeValueProto.Builder builder = AttributeValueProto.newBuilder() + .setLastUpdateTs(attributeKvEntry.getLastUpdateTs()); + switch (attributeKvEntry.getDataType()) { + case BOOLEAN: + attributeKvEntry.getBooleanValue().ifPresent(builder::setBoolV); + builder.setHasV(attributeKvEntry.getBooleanValue().isPresent()); + builder.setType(KeyValueType.BOOLEAN_V); + break; + case STRING: + attributeKvEntry.getStrValue().ifPresent(builder::setStringV); + builder.setHasV(attributeKvEntry.getStrValue().isPresent()); + builder.setType(KeyValueType.STRING_V); + break; + case DOUBLE: + attributeKvEntry.getDoubleValue().ifPresent(builder::setDoubleV); + builder.setHasV(attributeKvEntry.getDoubleValue().isPresent()); + builder.setType(KeyValueType.DOUBLE_V); + break; + case LONG: + attributeKvEntry.getLongValue().ifPresent(builder::setLongV); + builder.setHasV(attributeKvEntry.getLongValue().isPresent()); + builder.setType(KeyValueType.LONG_V); + break; + case JSON: + attributeKvEntry.getJsonValue().ifPresent(builder::setJsonV); + builder.setHasV(attributeKvEntry.getJsonValue().isPresent()); + builder.setType(KeyValueType.JSON_V); + break; + + } + return builder.build().toByteArray(); + } + + @Override + public AttributeKvEntry deserialize(AttributeCacheKey key, byte[] bytes) throws SerializationException { + try { + AttributeValueProto proto = AttributeValueProto.parseFrom(bytes); + boolean hasValue = proto.getHasV(); + KvEntry entry; + switch (proto.getType()) { + case BOOLEAN_V: + entry = new BooleanDataEntry(key.getKey(), hasValue ? proto.getBoolV() : null); + break; + case LONG_V: + entry = new LongDataEntry(key.getKey(), hasValue ? proto.getLongV() : null); + break; + case DOUBLE_V: + entry = new DoubleDataEntry(key.getKey(), hasValue ? proto.getDoubleV() : null); + break; + case STRING_V: + entry = new StringDataEntry(key.getKey(), hasValue ? proto.getStringV() : null); + break; + case JSON_V: + entry = new JsonDataEntry(key.getKey(), hasValue ? proto.getJsonV() : null); + break; + default: + throw new InvalidProtocolBufferException("Unrecognized type: " + proto.getType() + " !"); + } + return new BaseAttributeKvEntry(proto.getLastUpdateTs(), entry); + } catch (InvalidProtocolBufferException e) { + throw new SerializationException(e.getMessage()); + } + } + }); } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java index d79537e564..0b2abcf517 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerDao.java @@ -16,10 +16,12 @@ package org.thingsboard.server.dao.customer; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; import java.util.Optional; @@ -28,7 +30,7 @@ import java.util.UUID; /** * The Interface CustomerDao. */ -public interface CustomerDao extends Dao, TenantEntityDao { +public interface CustomerDao extends Dao, TenantEntityDao, ExportableEntityDao { /** * Save or update customer object @@ -37,7 +39,7 @@ public interface CustomerDao extends Dao, TenantEntityDao { * @return saved customer object */ Customer save(TenantId tenantId, Customer customer); - + /** * Find customers by tenant id and page link. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index c79ec44880..b7ac0b0f17 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -31,7 +31,6 @@ import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -64,9 +63,6 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom @Autowired private DeviceService deviceService; - @Autowired - private EntityViewService entityViewService; - @Autowired private DashboardService dashboardService; @@ -102,9 +98,15 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom public Customer saveCustomer(Customer customer) { log.trace("Executing saveCustomer [{}]", customer); customerValidator.validate(customer, Customer::getTenantId); - Customer savedCustomer = customerDao.save(customer.getTenantId(), customer); - dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId()); - return savedCustomer; + try { + Customer savedCustomer = customerDao.save(customer.getTenantId(), customer); + dashboardService.updateCustomerDashboards(savedCustomer.getTenantId(), savedCustomer.getId()); + return savedCustomer; + } catch (Exception e) { + checkConstraintViolation(e, "customer_external_id_unq_key", "Customer with such external id already exists!"); + throw e; + } + } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java index dfe9152484..4246137d9a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardDao.java @@ -16,14 +16,19 @@ package org.thingsboard.server.dao.dashboard; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; +import java.util.List; +import java.util.UUID; + /** * The Interface DashboardDao. */ -public interface DashboardDao extends Dao, TenantEntityDao { +public interface DashboardDao extends Dao, TenantEntityDao, ExportableEntityDao { /** * Save or update dashboard object @@ -32,4 +37,7 @@ public interface DashboardDao extends Dao, TenantEntityDao { * @return saved dashboard object */ Dashboard save(TenantId tenantId, Dashboard dashboard); + + List findByTenantIdAndTitle(UUID tenantId, String title); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index ee5c44e20f..9ac647696a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -40,6 +40,8 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import java.util.List; + import static org.thingsboard.server.dao.service.Validator.validateId; @Service @@ -56,7 +58,7 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb @Autowired private CustomerDao customerDao; - + @Autowired private EdgeDao edgeDao; @@ -95,7 +97,12 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb public Dashboard saveDashboard(Dashboard dashboard) { log.trace("Executing saveDashboard [{}]", dashboard); dashboardValidator.validate(dashboard, DashboardInfo::getTenantId); - return dashboardDao.save(dashboard.getTenantId(), dashboard); + try { + return dashboardDao.save(dashboard.getTenantId(), dashboard); + } catch (Exception e) { + checkConstraintViolation(e, "dashboard_external_id_unq_key", "Dashboard with such external id already exists!"); + throw e; + } } @Override @@ -279,19 +286,24 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name); } + @Override + public List findTenantDashboardsByTitle(TenantId tenantId, String title) { + return dashboardDao.findByTenantIdAndTitle(tenantId.getId(), title); + } + private PaginatedRemover tenantDashboardsRemover = new PaginatedRemover() { - @Override - protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { - return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink); - } + @Override + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { + return dashboardInfoDao.findDashboardsByTenantId(id.getId(), pageLink); + } - @Override - protected void removeEntity(TenantId tenantId, DashboardInfo entity) { - deleteDashboard(tenantId, new DashboardId(entity.getUuidId())); - } - }; + @Override + protected void removeEntity(TenantId tenantId, DashboardInfo entity) { + deleteDashboard(tenantId, new DashboardId(entity.getUuidId())); + } + }; private class CustomerDashboardsUnassigner extends PaginatedRemover { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java index 1d8a9455dc..d9f10a325b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsRedisCache.java @@ -20,16 +20,16 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("DeviceCredentialsCache") public class DeviceCredentialsRedisCache extends RedisTbTransactionalCache { public DeviceCredentialsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.DEVICE_CREDENTIALS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 85f66e4b67..c96f196d44 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -20,11 +20,13 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; import java.util.List; @@ -35,7 +37,7 @@ import java.util.UUID; * The Interface DeviceDao. * */ -public interface DeviceDao extends Dao, TenantEntityDao { +public interface DeviceDao extends Dao, TenantEntityDao, ExportableEntityDao { /** * Find device info by id. diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java index bf61cbdce5..4d4c73728f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileDao.java @@ -17,14 +17,16 @@ package org.thingsboard.server.dao.device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import java.util.UUID; -public interface DeviceProfileDao extends Dao { +public interface DeviceProfileDao extends Dao, ExportableEntityDao { DeviceProfileInfo findDeviceProfileInfoById(TenantId tenantId, UUID deviceProfileId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java index f3d7ad634f..6c6e3d7c00 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileRedisCache.java @@ -20,16 +20,16 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("DeviceProfileCache") public class DeviceProfileRedisCache extends RedisTbTransactionalCache { public DeviceProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.DEVICE_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java index df5a49c1e1..b8308979c1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceProfileServiceImpl.java @@ -18,9 +18,8 @@ package org.thingsboard.server.dao.device; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -47,8 +46,7 @@ import org.thingsboard.server.dao.service.Validator; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.Map; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -73,11 +71,10 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService deviceProfileValidator; + @Lazy @Autowired private QueueService queueService; - private final Lock findOrCreateLock = new ReentrantLock(); - @TransactionalEventListener(classes = DeviceProfileEvictEvent.class) @Override public void handleEvictEvent(DeviceProfileEvictEvent event) { @@ -124,27 +121,17 @@ public class DeviceProfileServiceImpl extends AbstractCachedEntityService entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), deviceId).get(); - if (entityViews != null && !entityViews.isEmpty()) { - throw new DataValidationException("Can't delete device that has entity views!"); - } - } catch (ExecutionException | InterruptedException e) { - log.error("Exception while finding entity views for deviceId [{}]", deviceId, e); - throw new RuntimeException("Exception while finding entity views for deviceId [" + deviceId + "]", e); + List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(device.getTenantId(), deviceId); + if (entityViews != null && !entityViews.isEmpty()) { + throw new DataValidationException("Can't delete device that has entity views!"); } DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId); @@ -523,14 +518,9 @@ public class DeviceServiceImpl extends AbstractCachedEntityService entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get(); - if (!CollectionUtils.isEmpty(entityViews)) { - throw new DataValidationException("Can't assign device that has entity views to another tenant!"); - } - } catch (ExecutionException | InterruptedException e) { - log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e); - throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e); + List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(device.getTenantId(), device.getId()); + if (!CollectionUtils.isEmpty(entityViews)) { + throw new DataValidationException("Can't assign device that has entity views to another tenant!"); } eventService.removeEvents(device.getTenantId(), device.getId()); @@ -541,6 +531,7 @@ public class DeviceServiceImpl extends AbstractCachedEntityService { public EdgeRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.EDGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.EDGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 271c74a3e1..54bcace223 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.edge; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; @@ -26,9 +25,8 @@ import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; @@ -48,7 +46,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.relation.RelationService; @@ -64,6 +62,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.stream.Collectors; import static org.thingsboard.server.dao.DaoUtil.toUUIDs; @@ -80,8 +79,6 @@ public class EdgeServiceImpl extends AbstractCachedEntityService findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) { log.trace("[{}] Executing findRelatedEdgeIdsByEntityId [{}] [{}]", tenantId, entityId, pageLink); - if (EntityType.TENANT.equals(entityId.getEntityType()) || - EntityType.CUSTOMER.equals(entityId.getEntityType()) || - EntityType.DEVICE_PROFILE.equals(entityId.getEntityType())) { - if (EntityType.TENANT.equals(entityId.getEntityType()) || - EntityType.DEVICE_PROFILE.equals(entityId.getEntityType())) { + switch (entityId.getEntityType()) { + case TENANT: + case DEVICE_PROFILE: + case OTA_PACKAGE: return convertToEdgeIds(findEdgesByTenantId(tenantId, pageLink)); - } else { + case CUSTOMER: return convertToEdgeIds(findEdgesByTenantIdAndCustomerId(tenantId, new CustomerId(entityId.getId()), pageLink)); - } - } else { - switch (entityId.getEntityType()) { - case EDGE: - List edgeIds = Collections.singletonList(new EdgeId(entityId.getId())); - return new PageData<>(edgeIds, 1, 1, false); - case DEVICE: - case ASSET: - case ENTITY_VIEW: - case DASHBOARD: - case RULE_CHAIN: - return convertToEdgeIds(findEdgesByTenantIdAndEntityId(tenantId, entityId, pageLink)); - case USER: - User userById = userService.findUserById(tenantId, new UserId(entityId.getId())); - if (userById == null) { - return createEmptyEdgeIdPageData(); - } - if (userById.getCustomerId() == null || userById.getCustomerId().isNullUid()) { - return convertToEdgeIds(findEdgesByTenantId(tenantId, pageLink)); - } else { - return convertToEdgeIds(findEdgesByTenantIdAndCustomerId(tenantId, userById.getCustomerId(), pageLink)); - } - default: - log.warn("[{}] Unsupported entity type {}", tenantId, entityId.getEntityType()); + case EDGE: + List edgeIds = Collections.singletonList(new EdgeId(entityId.getId())); + return new PageData<>(edgeIds, 1, 1, false); + case DEVICE: + case ASSET: + case ENTITY_VIEW: + case DASHBOARD: + case RULE_CHAIN: + return convertToEdgeIds(findEdgesByTenantIdAndEntityId(tenantId, entityId, pageLink)); + case USER: + User userById = userService.findUserById(tenantId, new UserId(entityId.getId())); + if (userById == null) { return createEmptyEdgeIdPageData(); - } + } + if (userById.getCustomerId() == null || userById.getCustomerId().isNullUid()) { + return convertToEdgeIds(findEdgesByTenantId(tenantId, pageLink)); + } else { + return convertToEdgeIds(findEdgesByTenantIdAndCustomerId(tenantId, userById.getCustomerId(), pageLink)); + } + default: + log.warn("[{}] Unsupported entity type {}", tenantId, entityId.getEntityType()); + return createEmptyEdgeIdPageData(); } } @@ -453,16 +445,19 @@ public class EdgeServiceImpl extends AbstractCachedEntityService edgeRuleChains = findEdgeRuleChains(tenantId, edgeId); List edgeRuleChainIds = edgeRuleChains.stream().map(IdBased::getId).collect(Collectors.toList()); - ObjectNode result = mapper.createObjectNode(); + ObjectNode result = JacksonUtil.OBJECT_MAPPER.createObjectNode(); for (RuleChain edgeRuleChain : edgeRuleChains) { - List connectionInfos = - ruleChainService.loadRuleChainMetaData(edgeRuleChain.getTenantId(), edgeRuleChain.getId()).getRuleChainConnections(); - if (connectionInfos != null && !connectionInfos.isEmpty()) { + List ruleNodes = + ruleChainService.loadRuleChainMetaData(edgeRuleChain.getTenantId(), edgeRuleChain.getId()).getNodes(); + if (ruleNodes != null && !ruleNodes.isEmpty()) { List connectedRuleChains = - connectionInfos.stream().map(RuleChainConnectionInfo::getTargetRuleChainId).collect(Collectors.toList()); + ruleNodes.stream() + .filter(rn -> rn.getType().equals(tbRuleChainInputNodeClassName)) + .map(rn -> new RuleChainId(UUID.fromString(rn.getConfiguration().get("ruleChainId").asText()))) + .collect(Collectors.toList()); List missingRuleChains = new ArrayList<>(); for (RuleChainId connectedRuleChain : connectedRuleChains) { if (!edgeRuleChainIds.contains(connectedRuleChain)) { @@ -471,7 +466,7 @@ public class EdgeServiceImpl extends AbstractCachedEntityService extractConstraintViolationException(Exception t) { + protected static Optional extractConstraintViolationException(Exception t) { if (t instanceof ConstraintViolationException) { return Optional.of((ConstraintViolationException) t); } else if (t.getCause() instanceof ConstraintViolationException) { @@ -83,21 +87,40 @@ public abstract class AbstractEntityService { } } - protected void checkAssignedEntityViewsToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) { - try { - List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(tenantId, entityId).get(); - if (entityViews != null && !entityViews.isEmpty()) { - EntityView entityView = entityViews.get(0); - // TODO: @voba - refactor this blocking operation - Boolean relationExists = relationService.checkRelation(tenantId, edgeId, entityView.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE).get(); - if (relationExists) { - throw new DataValidationException("Can't unassign device/asset from edge that is related to entity view and entity view is assigned to edge!"); + public static final void checkConstraintViolation(Exception t, String constraintName, String constraintMessage) { + checkConstraintViolation(t, Collections.singletonMap(constraintName, constraintMessage)); + } + + public static final void checkConstraintViolation(Exception t, String constraintName1, String constraintMessage1, String constraintName2, String constraintMessage2) { + checkConstraintViolation(t, Map.of(constraintName1, constraintMessage1, constraintName2, constraintMessage2)); + } + + public static final void checkConstraintViolation(Exception t, Map constraints) { + var exOpt = extractConstraintViolationException(t); + if (exOpt.isPresent()) { + var ex = exOpt.get(); + if (StringUtils.isNotEmpty(ex.getConstraintName())) { + var constraintName = ex.getConstraintName(); + for (var constraintMessage : constraints.entrySet()) { + if (constraintName.equals(constraintMessage.getKey())) { + throw new DataValidationException(constraintMessage.getValue()); + } } } - } catch (Exception e) { - log.error("[{}] Exception while finding entity views for entityId [{}]", tenantId, entityId, e); - throw new RuntimeException("Exception while finding entity views for entityId [" + entityId + "]", e); + } + } + + protected void checkAssignedEntityViewsToEdge(TenantId tenantId, EntityId entityId, EdgeId edgeId) { + List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityId(tenantId, entityId); + if (entityViews != null && !entityViews.isEmpty()) { + EntityView entityView = entityViews.get(0); + Boolean relationExists = relationService.checkRelation( + tenantId, edgeId, entityView.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.EDGE + ); + if (relationExists) { + throw new DataValidationException("Can't unassign device/asset from edge that is related to entity view and entity view is assigned to edge!"); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java index f44bafe937..75a9c122e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewDao.java @@ -19,10 +19,12 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; +import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import java.util.List; import java.util.Optional; @@ -31,7 +33,7 @@ import java.util.UUID; /** * Created by Victor Basanets on 8/28/2017. */ -public interface EntityViewDao extends Dao { +public interface EntityViewDao extends Dao, ExportableEntityDao { /** * Find entity view info by id. diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java index e2ade492ff..aeb8a93594 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewRedisCache.java @@ -22,13 +22,13 @@ import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.cache.TbFSTRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("EntityViewCache") public class EntityViewRedisCache extends RedisTbTransactionalCache { public EntityViewRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.ENTITY_VIEW_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.ENTITY_VIEW_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java index 04bdb6697a..f71f2cc7a9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java @@ -22,8 +22,6 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; @@ -53,7 +51,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -68,7 +65,6 @@ import static org.thingsboard.server.dao.service.Validator.validateString; public class EntityViewServiceImpl extends AbstractCachedEntityService implements EntityViewService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - public static final String INCORRECT_PAGE_LINK = "Incorrect page link "; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; public static final String INCORRECT_ENTITY_VIEW_ID = "Incorrect entityViewId "; public static final String INCORRECT_EDGE_ID = "Incorrect edgeId "; @@ -102,9 +98,15 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService new EntityViewCacheValue(null, v), true)); } + @Override + public List findEntityViewsByTenantIdAndEntityId(TenantId tenantId, EntityId entityId) { + log.trace("Executing findEntityViewsByTenantIdAndEntityId, tenantId [{}], entityId [{}]", tenantId, entityId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(entityId.getId(), "Incorrect entityId" + entityId); + + return cache.getAndPutInTransaction(EntityViewCacheKey.byEntityId(tenantId, entityId), + () -> entityViewDao.findEntityViewsByTenantIdAndEntityId(tenantId.getId(), entityId.getId()), + EntityViewCacheValue::getEntityViews, v -> new EntityViewCacheValue(null, v), true); + } + @Override public void deleteEntityView(TenantId tenantId, EntityViewId entityViewId) { log.trace("Executing deleteEntityView [{}]", entityViewId); @@ -320,15 +333,10 @@ public class EntityViewServiceImpl extends AbstractCachedEntityService implements BaseEntity { @Column(name = ModelConstants.ID_PROPERTY, columnDefinition = "uuid") protected UUID id; - @Column(name = ModelConstants.CREATED_TIME_PROPERTY) + @Column(name = ModelConstants.CREATED_TIME_PROPERTY, updatable = false) protected long createdTime; @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index f6f838eb62..0b4a65f43c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -90,6 +90,8 @@ public class ModelConstants { * Cassandra admin_settings constants. */ public static final String ADMIN_SETTINGS_COLUMN_FAMILY_NAME = "admin_settings"; + + public static final String ADMIN_SETTINGS_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String ADMIN_SETTINGS_KEY_PROPERTY = "key"; public static final String ADMIN_SETTINGS_JSON_VALUE_PROPERTY = "json_value"; @@ -179,6 +181,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; public static final String DEVICE_PROFILE_DEFAULT_DASHBOARD_ID_PROPERTY = "default_dashboard_id"; public static final String DEVICE_PROFILE_DEFAULT_QUEUE_ID_PROPERTY = "default_queue_id"; + public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name"; public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_PROFILE_SOFTWARE_ID_PROPERTY = "software_id"; @@ -559,6 +562,8 @@ public class ModelConstants { public static final String EDGE_EVENT_BY_ID_VIEW_NAME = "edge_event_by_id"; + public static final String EXTERNAL_ID_PROPERTY = "external_id"; + /** * User auth settings constants. * */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java index 4405768f7c..0936ca2855 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAssetEntity.java @@ -38,6 +38,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPER import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.EXTERNAL_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @Data @@ -68,6 +69,9 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = EXTERNAL_ID_PROPERTY) + private UUID externalId; + public AbstractAssetEntity() { super(); } @@ -87,6 +91,9 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity this.type = asset.getType(); this.label = asset.getLabel(); this.additionalInfo = asset.getAdditionalInfo(); + if (asset.getExternalId() != null) { + this.externalId = asset.getExternalId().getId(); + } } public AbstractAssetEntity(AssetEntity assetEntity) { @@ -99,6 +106,7 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity this.label = assetEntity.getLabel(); this.searchText = assetEntity.getSearchText(); this.additionalInfo = assetEntity.getAdditionalInfo(); + this.externalId = assetEntity.getExternalId(); } @Override @@ -128,6 +136,9 @@ public abstract class AbstractAssetEntity extends BaseSqlEntity asset.setType(type); asset.setLabel(label); asset.setAdditionalInfo(additionalInfo); + if (externalId != null) { + asset.setExternalId(new AssetId(externalId)); + } return asset; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 717ba1f9e3..dbc0a057ed 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -84,6 +84,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti @Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode deviceData; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY, columnDefinition = "uuid") + private UUID externalId; + public AbstractDeviceEntity() { super(); } @@ -113,6 +116,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti this.type = device.getType(); this.label = device.getLabel(); this.additionalInfo = device.getAdditionalInfo(); + if (device.getExternalId() != null) { + this.externalId = device.getExternalId().getId(); + } } public AbstractDeviceEntity(DeviceEntity deviceEntity) { @@ -129,6 +135,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti this.additionalInfo = deviceEntity.getAdditionalInfo(); this.firmwareId = deviceEntity.getFirmwareId(); this.softwareId = deviceEntity.getSoftwareId(); + this.externalId = deviceEntity.getExternalId(); } @Override @@ -164,6 +171,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti device.setType(type); device.setLabel(label); device.setAdditionalInfo(additionalInfo); + if (externalId != null) { + device.setExternalId(new DeviceId(externalId)); + } return device; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java index a8a31cd892..cc68adb2a1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEntityViewEntity.java @@ -89,6 +89,9 @@ public abstract class AbstractEntityViewEntity extends Bas @Column(name = ModelConstants.ENTITY_VIEW_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + private static final ObjectMapper mapper = new ObjectMapper(); public AbstractEntityViewEntity() { @@ -121,6 +124,9 @@ public abstract class AbstractEntityViewEntity extends Bas this.endTs = entityView.getEndTimeMs(); this.searchText = entityView.getSearchText(); this.additionalInfo = entityView.getAdditionalInfo(); + if (entityView.getExternalId() != null) { + this.externalId = entityView.getExternalId().getId(); + } } public AbstractEntityViewEntity(EntityViewEntity entityViewEntity) { @@ -137,6 +143,7 @@ public abstract class AbstractEntityViewEntity extends Bas this.endTs = entityViewEntity.getEndTs(); this.searchText = entityViewEntity.getSearchText(); this.additionalInfo = entityViewEntity.getAdditionalInfo(); + this.externalId = entityViewEntity.getExternalId(); } @Override @@ -172,6 +179,9 @@ public abstract class AbstractEntityViewEntity extends Bas entityView.setStartTimeMs(startTs); entityView.setEndTimeMs(endTs); entityView.setAdditionalInfo(additionalInfo); + if (externalId != null) { + entityView.setExternalId(new EntityViewId(externalId)); + } return entityView; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java index 49d5002af2..4da17d2c34 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AdminSettingsEntity.java @@ -22,14 +22,18 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.AdminSettingsId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import java.util.UUID; + import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_COLUMN_FAMILY_NAME; import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_JSON_VALUE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_KEY_PROPERTY; @@ -41,6 +45,9 @@ import static org.thingsboard.server.dao.model.ModelConstants.ADMIN_SETTINGS_KEY @Table(name = ADMIN_SETTINGS_COLUMN_FAMILY_NAME) public final class AdminSettingsEntity extends BaseSqlEntity implements BaseEntity { + @Column(name = ModelConstants.ADMIN_SETTINGS_TENANT_ID_PROPERTY) + private UUID tenantId; + @Column(name = ADMIN_SETTINGS_KEY_PROPERTY) private String key; @@ -57,6 +64,7 @@ public final class AdminSettingsEntity extends BaseSqlEntity impl this.setUuid(adminSettings.getId().getId()); } this.setCreatedTime(adminSettings.getCreatedTime()); + this.tenantId = adminSettings.getTenantId().getId(); this.key = adminSettings.getKey(); this.jsonValue = adminSettings.getJsonValue(); } @@ -65,6 +73,7 @@ public final class AdminSettingsEntity extends BaseSqlEntity impl public AdminSettings toData() { AdminSettings adminSettings = new AdminSettings(new AdminSettingsId(id)); adminSettings.setCreatedTime(createdTime); + adminSettings.setTenantId(TenantId.fromUUID(tenantId)); adminSettings.setKey(key); adminSettings.setJsonValue(jsonValue); return adminSettings; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java index 61b21ecc4f..3dec26afd9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/CustomerEntity.java @@ -77,6 +77,9 @@ public final class CustomerEntity extends BaseSqlEntity implements Sea @Column(name = ModelConstants.CUSTOMER_ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public CustomerEntity() { super(); } @@ -97,6 +100,9 @@ public final class CustomerEntity extends BaseSqlEntity implements Sea this.phone = customer.getPhone(); this.email = customer.getEmail(); this.additionalInfo = customer.getAdditionalInfo(); + if (customer.getExternalId() != null) { + this.externalId = customer.getExternalId().getId(); + } } @Override @@ -124,6 +130,9 @@ public final class CustomerEntity extends BaseSqlEntity implements Sea customer.setPhone(phone); customer.setEmail(email); customer.setAdditionalInfo(additionalInfo); + if (externalId != null) { + customer.setExternalId(new CustomerId(externalId)); + } return customer; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java index 45b4bf9210..ccaea5524e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java @@ -78,6 +78,9 @@ public final class DashboardEntity extends BaseSqlEntity implements S @Column(name = ModelConstants.DASHBOARD_CONFIGURATION_PROPERTY) private JsonNode configuration; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public DashboardEntity() { super(); } @@ -102,6 +105,9 @@ public final class DashboardEntity extends BaseSqlEntity implements S this.mobileHide = dashboard.isMobileHide(); this.mobileOrder = dashboard.getMobileOrder(); this.configuration = dashboard.getConfiguration(); + if (dashboard.getExternalId() != null) { + this.externalId = dashboard.getExternalId().getId(); + } } @Override @@ -133,6 +139,9 @@ public final class DashboardEntity extends BaseSqlEntity implements S dashboard.setMobileHide(mobileHide); dashboard.setMobileOrder(mobileOrder); dashboard.setConfiguration(configuration); + if (externalId != null) { + dashboard.setExternalId(new DashboardId(externalId)); + } return dashboard; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 9dc5c9780e..f2d1970238 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -88,8 +88,8 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_DASHBOARD_ID_PROPERTY) private UUID defaultDashboardId; - @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_ID_PROPERTY) - private UUID defaultQueueId; + @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY) + private String defaultQueueName; @Type(type = "jsonb") @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") @@ -104,6 +104,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_SOFTWARE_ID_PROPERTY) private UUID softwareId; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public DeviceProfileEntity() { super(); } @@ -130,9 +133,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (deviceProfile.getDefaultDashboardId() != null) { this.defaultDashboardId = deviceProfile.getDefaultDashboardId().getId(); } - if (deviceProfile.getDefaultQueueId() != null) { - this.defaultQueueId = deviceProfile.getDefaultQueueId().getId(); - } + this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); if (deviceProfile.getFirmwareId() != null) { this.firmwareId = deviceProfile.getFirmwareId().getId(); @@ -140,6 +141,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (deviceProfile.getSoftwareId() != null) { this.softwareId = deviceProfile.getSoftwareId().getId(); } + if (deviceProfile.getExternalId() != null) { + this.externalId = deviceProfile.getExternalId().getId(); + } } @Override @@ -170,6 +174,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setProvisionType(provisionType); deviceProfile.setDescription(description); deviceProfile.setDefault(isDefault); + deviceProfile.setDefaultQueueName(defaultQueueName); deviceProfile.setProfileData(JacksonUtil.convertValue(profileData, DeviceProfileData.class)); if (defaultRuleChainId != null) { deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); @@ -177,18 +182,17 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (defaultDashboardId != null) { deviceProfile.setDefaultDashboardId(new DashboardId(defaultDashboardId)); } - if (defaultQueueId != null) { - deviceProfile.setDefaultQueueId(new QueueId(defaultQueueId)); - } deviceProfile.setProvisionDeviceKey(provisionDeviceKey); if (firmwareId != null) { deviceProfile.setFirmwareId(new OtaPackageId(firmwareId)); } - if (softwareId != null) { deviceProfile.setSoftwareId(new OtaPackageId(softwareId)); } + if (externalId != null) { + deviceProfile.setExternalId(new DeviceProfileId(externalId)); + } return deviceProfile; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java index 09ed75b5e5..88c8c79fdc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleChainEntity.java @@ -75,6 +75,9 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public RuleChainEntity() { } @@ -94,6 +97,9 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT this.debugMode = ruleChain.isDebugMode(); this.configuration = ruleChain.getConfiguration(); this.additionalInfo = ruleChain.getAdditionalInfo(); + if (ruleChain.getExternalId() != null) { + this.externalId = ruleChain.getExternalId().getId(); + } } @Override @@ -120,6 +126,9 @@ public class RuleChainEntity extends BaseSqlEntity implements SearchT ruleChain.setDebugMode(debugMode); ruleChain.setConfiguration(configuration); ruleChain.setAdditionalInfo(additionalInfo); + if (externalId != null) { + ruleChain.setExternalId(new RuleChainId(externalId)); + } return ruleChain; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java index 04b7fd4ecb..70e8497c31 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java @@ -64,6 +64,9 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex @Column(name = ModelConstants.DEBUG_MODE) private boolean debugMode; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public RuleNodeEntity() { } @@ -81,6 +84,9 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex this.searchText = ruleNode.getName(); this.configuration = ruleNode.getConfiguration(); this.additionalInfo = ruleNode.getAdditionalInfo(); + if (ruleNode.getExternalId() != null) { + this.externalId = ruleNode.getExternalId().getId(); + } } @Override @@ -105,6 +111,9 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex ruleNode.setDebugMode(debugMode); ruleNode.setConfiguration(configuration); ruleNode.setAdditionalInfo(additionalInfo); + if (externalId != null) { + ruleNode.setExternalId(new RuleNodeId(externalId)); + } return ruleNode; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java index 6f7b094464..db584b9ff3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java @@ -53,9 +53,6 @@ public final class TenantProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.TENANT_PROFILE_IS_DEFAULT_PROPERTY) private boolean isDefault; - @Column(name = ModelConstants.TENANT_PROFILE_ISOLATED_TB_CORE) - private boolean isolatedTbCore; - @Column(name = ModelConstants.TENANT_PROFILE_ISOLATED_TB_RULE_ENGINE) private boolean isolatedTbRuleEngine; @@ -75,7 +72,6 @@ public final class TenantProfileEntity extends BaseSqlEntity impl this.name = tenantProfile.getName(); this.description = tenantProfile.getDescription(); this.isDefault = tenantProfile.isDefault(); - this.isolatedTbCore = tenantProfile.isIsolatedTbCore(); this.isolatedTbRuleEngine = tenantProfile.isIsolatedTbRuleEngine(); this.profileData = JacksonUtil.convertValue(tenantProfile.getProfileData(), ObjectNode.class); } @@ -101,7 +97,6 @@ public final class TenantProfileEntity extends BaseSqlEntity impl tenantProfile.setName(name); tenantProfile.setDescription(description); tenantProfile.setDefault(isDefault); - tenantProfile.setIsolatedTbCore(isolatedTbCore); tenantProfile.setIsolatedTbRuleEngine(isolatedTbRuleEngine); tenantProfile.setProfileData(JacksonUtil.convertValue(profileData, TenantProfileData.class)); return tenantProfile; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java index 5887c6f61e..cbd6068d7f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/WidgetsBundleEntity.java @@ -54,6 +54,9 @@ public final class WidgetsBundleEntity extends BaseSqlEntity impl @Column(name = ModelConstants.WIDGETS_BUNDLE_DESCRIPTION) private String description; + @Column(name = ModelConstants.EXTERNAL_ID_PROPERTY) + private UUID externalId; + public WidgetsBundleEntity() { super(); } @@ -70,6 +73,9 @@ public final class WidgetsBundleEntity extends BaseSqlEntity impl this.title = widgetsBundle.getTitle(); this.image = widgetsBundle.getImage(); this.description = widgetsBundle.getDescription(); + if (widgetsBundle.getExternalId() != null) { + this.externalId = widgetsBundle.getExternalId().getId(); + } } @Override @@ -93,6 +99,9 @@ public final class WidgetsBundleEntity extends BaseSqlEntity impl widgetsBundle.setTitle(title); widgetsBundle.setImage(image); widgetsBundle.setDescription(description); + if (externalId != null) { + widgetsBundle.setExternalId(new WidgetsBundleId(externalId)); + } return widgetsBundle; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java index da440cb06d..2fa019e663 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateReadExecutor.java @@ -22,8 +22,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.stats.DefaultCounter; +import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.NoSqlAnyDao; @@ -47,14 +51,13 @@ public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecu @Value("${cassandra.query.dispatcher_threads:2}") int dispatcherThreads, @Value("${cassandra.query.callback_threads:4}") int callbackThreads, @Value("${cassandra.query.poll_ms:50}") long pollMs, - @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled, - @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, @Autowired StatsFactory statsFactory, - @Autowired EntityService entityService) { - super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory, - entityService, printTenantNames); + @Autowired EntityService entityService, + @Autowired TbTenantProfileCache tenantProfileCache) { + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory, + entityService, tenantProfileCache, printTenantNames); } @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java index b6a522c931..366df9ae3d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateWriteExecutor.java @@ -24,6 +24,7 @@ import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.entity.EntityService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.NoSqlAnyDao; @@ -47,14 +48,13 @@ public class CassandraBufferedRateWriteExecutor extends AbstractBufferedRateExec @Value("${cassandra.query.dispatcher_threads:2}") int dispatcherThreads, @Value("${cassandra.query.callback_threads:4}") int callbackThreads, @Value("${cassandra.query.poll_ms:50}") long pollMs, - @Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled, - @Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration, @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, @Autowired StatsFactory statsFactory, - @Autowired EntityService entityService) { - super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory, - entityService, printTenantNames); + @Autowired EntityService entityService, + @Autowired TbTenantProfileCache tenantProfileCache) { + super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory, + entityService, tenantProfileCache, printTenantNames); } @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java index 4d0bb18a43..841ff82441 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java @@ -22,8 +22,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.OtaPackage; @@ -42,8 +40,6 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import java.nio.ByteBuffer; -import java.util.Collections; -import java.util.List; import java.util.Optional; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -238,18 +234,4 @@ public class BaseOtaPackageService extends AbstractCachedEntityService extractConstraintViolationException(Exception t) { - if (t instanceof ConstraintViolationException) { - return Optional.of((ConstraintViolationException) t); - } else if (t.getCause() instanceof ConstraintViolationException) { - return Optional.of((ConstraintViolationException) (t.getCause())); - } else { - return Optional.empty(); - } - } - - private static List toOtaPackageInfoKey(OtaPackageId otaPackageId) { - return Collections.singletonList(otaPackageId); - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java index 1257932b79..418671f1e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageRedisCache.java @@ -20,16 +20,16 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("OtaPackageCache") public class OtaPackageRedisCache extends RedisTbTransactionalCache { public OtaPackageRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.OTA_PACKAGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.OTA_PACKAGE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java index a0254a4923..45b5b2cc7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/BaseQueueService.java @@ -19,6 +19,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.QueueId; @@ -43,6 +44,7 @@ public class BaseQueueService extends AbstractEntityService implements QueueServ @Autowired private QueueDao queueDao; + @Lazy @Autowired private TbTenantProfileCache tenantProfileCache; diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 912a3393c4..f60920a31d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.relation; import com.google.common.base.Function; +import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; @@ -24,12 +25,11 @@ import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; import org.springframework.dao.ConcurrencyFailureException; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.util.StringUtils; import org.thingsboard.server.cache.TbTransactionalCache; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; @@ -90,7 +90,14 @@ public class BaseRelationService implements RelationService { } @Override - public ListenableFuture checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { + public ListenableFuture checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { + log.trace("Executing checkRelationAsync [{}][{}][{}][{}]", from, to, relationType, typeGroup); + validate(from, to, relationType, typeGroup); + return relationDao.checkRelationAsync(tenantId, from, to, relationType, typeGroup); + } + + @Override + public boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { log.trace("Executing checkRelation [{}][{}][{}][{}]", from, to, relationType, typeGroup); validate(from, to, relationType, typeGroup); return relationDao.checkRelation(tenantId, from, to, relationType, typeGroup); @@ -119,6 +126,20 @@ public class BaseRelationService implements RelationService { return result; } + @Override + public void saveRelations(TenantId tenantId, List relations) { + log.trace("Executing saveRelations [{}]", relations); + for (EntityRelation relation : relations) { + validate(relation); + } + for (List partition : Lists.partition(relations, 1024)) { + relationDao.saveRelations(tenantId, partition); + } + for (EntityRelation relation : relations) { + publishEvictEvent(EntityRelationEvent.from(relation)); + } + } + @Override public ListenableFuture saveRelationAsync(TenantId tenantId, EntityRelation relation) { log.trace("Executing saveRelationAsync [{}]", relation); @@ -167,68 +188,33 @@ public class BaseRelationService implements RelationService { return future; } + @Transactional @Override public void deleteEntityRelations(TenantId tenantId, EntityId entityId) { log.trace("Executing deleteEntityRelations [{}]", entityId); validate(entityId); - List inboundRelations = new ArrayList<>(); - for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelations.addAll(relationDao.findAllByTo(tenantId, entityId, typeGroup)); - } - - List outboundRelations = new ArrayList<>(); - for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - outboundRelations.addAll(relationDao.findAllByFrom(tenantId, entityId, typeGroup)); - } - - for (EntityRelation relation : inboundRelations) { - delete(tenantId, relation, true); - } + List inboundRelations = new ArrayList<>(relationDao.findAllByTo(tenantId, entityId)); + List outboundRelations = new ArrayList<>(relationDao.findAllByFrom(tenantId, entityId)); - for (EntityRelation relation : outboundRelations) { - delete(tenantId, relation, false); - } - - relationDao.deleteOutboundRelations(tenantId, entityId); - - } + if (!inboundRelations.isEmpty()) { + try { + relationDao.deleteInboundRelations(tenantId, entityId); + } catch (ConcurrencyFailureException e) { + log.debug("Concurrency exception while deleting relations [{}]", inboundRelations, e); + } - @Override - public ListenableFuture deleteEntityRelationsAsync(TenantId tenantId, EntityId entityId) { - log.trace("Executing deleteEntityRelationsAsync [{}]", entityId); - validate(entityId); - List>> inboundRelationsList = new ArrayList<>(); - for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsList.add(executor.submit(() -> relationDao.findAllByTo(tenantId, entityId, typeGroup))); + for (EntityRelation relation : inboundRelations) { + eventPublisher.publishEvent(EntityRelationEvent.from(relation)); + } } - ListenableFuture>> inboundRelations = Futures.allAsList(inboundRelationsList); + if (!outboundRelations.isEmpty()) { + relationDao.deleteOutboundRelations(tenantId, entityId); - List>> outboundRelationsList = new ArrayList<>(); - for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - outboundRelationsList.add(executor.submit(() -> relationDao.findAllByFrom(tenantId, entityId, typeGroup))); + for (EntityRelation relation : outboundRelations) { + eventPublisher.publishEvent(EntityRelationEvent.from(relation)); + } } - - ListenableFuture>> outboundRelations = Futures.allAsList(outboundRelationsList); - - ListenableFuture> inboundDeletions = Futures.transformAsync(inboundRelations, - relations -> { - List> results = deleteRelationGroupsAsync(tenantId, relations, true); - return Futures.allAsList(results); - }, MoreExecutors.directExecutor()); - - ListenableFuture> outboundDeletions = Futures.transformAsync(outboundRelations, - relations -> { - List> results = deleteRelationGroupsAsync(tenantId, relations, false); - return Futures.allAsList(results); - }, MoreExecutors.directExecutor()); - - ListenableFuture>> deletionsFuture = Futures.allAsList(inboundDeletions, outboundDeletions); - - return Futures.transform(Futures.transformAsync(deletionsFuture, - (deletions) -> relationDao.deleteOutboundRelationsAsync(tenantId, entityId), - MoreExecutors.directExecutor()), - result -> null, MoreExecutors.directExecutor()); } private List> deleteRelationGroupsAsync(TenantId tenantId, List> relations, boolean deleteFromDb) { @@ -252,18 +238,6 @@ public class BaseRelationService implements RelationService { } } - boolean delete(TenantId tenantId, EntityRelation relation, boolean deleteFromDb) { - eventPublisher.publishEvent(EntityRelationEvent.from(relation)); - if (deleteFromDb) { - try { - return relationDao.deleteRelation(tenantId, relation); - } catch (ConcurrencyFailureException e) { - log.debug("Concurrency exception while deleting relations [{}]", relation, e); - } - } - return false; - } - @Override public List findByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) { validate(from); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java index e15f4f3d69..a3e81c2652 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChainType; +import java.util.Collection; import java.util.List; /** @@ -31,18 +32,26 @@ public interface RelationDao { List findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); + List findAllByFrom(TenantId tenantId, EntityId from); + List findAllByFromAndType(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup); List findAllByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup); + List findAllByTo(TenantId tenantId, EntityId to); + List findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup); - ListenableFuture checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); + ListenableFuture checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); + + boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); EntityRelation getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); boolean saveRelation(TenantId tenantId, EntityRelation relation); + void saveRelations(TenantId tenantId, Collection relations); + ListenableFuture saveRelationAsync(TenantId tenantId, EntityRelation relation); boolean deleteRelation(TenantId tenantId, EntityRelation relation); @@ -53,7 +62,9 @@ public interface RelationDao { ListenableFuture deleteRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup); - boolean deleteOutboundRelations(TenantId tenantId, EntityId entity); + void deleteOutboundRelations(TenantId tenantId, EntityId entity); + + void deleteInboundRelations(TenantId tenantId, EntityId entity); ListenableFuture deleteOutboundRelationsAsync(TenantId tenantId, EntityId entity); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java index b849d36f63..043f0f9437 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationRedisCache.java @@ -20,15 +20,15 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.CacheSpecsMap; import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("RelationCache") public class RelationRedisCache extends RedisTbTransactionalCache { public RelationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.RELATIONS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.RELATIONS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 4be7227214..b511ab3da2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -58,6 +58,8 @@ import org.thingsboard.server.dao.service.Validator; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -68,7 +70,9 @@ import java.util.Set; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.DataConstants.TENANT; -import static org.thingsboard.server.dao.service.Validator.*; +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validatePageLink; +import static org.thingsboard.server.dao.service.Validator.validateString; /** * Created by igor on 3/12/18. @@ -80,7 +84,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC private static final int DEFAULT_PAGE_SIZE = 1000; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - + public static final String TB_RULE_CHAIN_INPUT_NODE = "org.thingsboard.rule.engine.flow.TbRuleChainInputNode"; @Autowired private RuleChainDao ruleChainDao; @@ -94,7 +98,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Transactional public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); - return ruleChainDao.save(ruleChain.getTenantId(), ruleChain); + try { + return ruleChainDao.save(ruleChain.getTenantId(), ruleChain); + } catch (Exception e) { + checkConstraintViolation(e, "rule_chain_external_id_unq_key", "Rule Chain with such external id already exists!"); + throw e; + } } @Override @@ -139,6 +148,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC List nodes = ruleChainMetaData.getNodes(); List toAddOrUpdate = new ArrayList<>(); List toDelete = new ArrayList<>(); + List relations = new ArrayList<>(); Map ruleNodeIndexMap = new HashMap<>(); if (nodes != null) { @@ -169,15 +179,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC for (RuleNode node : toAddOrUpdate) { node.setRuleChainId(ruleChain.getId()); RuleNode savedNode = ruleNodeDao.save(tenantId, node); - createRelation(tenantId, new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), + relations.add(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); int index = nodes.indexOf(node); nodes.set(index, savedNode); ruleNodeIndexMap.put(savedNode.getId(), index); } } - for (RuleNode node : toDelete) { - deleteRuleNode(tenantId, node.getId()); + if (!toDelete.isEmpty()) { + deleteRuleNodes(tenantId, toDelete); } RuleNodeId firstRuleNodeId = null; if (nodes != null) { @@ -194,7 +204,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC EntityId from = nodes.get(nodeConnection.getFromIndex()).getId(); EntityId to = nodes.get(nodeConnection.getToIndex()).getId(); String type = nodeConnection.getType(); - createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + relations.add(new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); } } if (ruleChainMetaData.getRuleChainConnections() != null) { @@ -220,7 +230,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC sourceRuleChainToRuleNode.setTo(targetNode.getId()); sourceRuleChainToRuleNode.setType(EntityRelation.CONTAINS_TYPE); sourceRuleChainToRuleNode.setTypeGroup(RelationTypeGroup.RULE_CHAIN); - relationService.saveRelation(tenantId, sourceRuleChainToRuleNode); + relations.add(sourceRuleChainToRuleNode); EntityRelation sourceRuleNodeToTargetRuleNode = new EntityRelation(); EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); @@ -228,11 +238,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC sourceRuleNodeToTargetRuleNode.setTo(targetNode.getId()); sourceRuleNodeToTargetRuleNode.setType(nodeToRuleChainConnection.getType()); sourceRuleNodeToTargetRuleNode.setTypeGroup(RelationTypeGroup.RULE_NODE); - relationService.saveRelation(tenantId, sourceRuleNodeToTargetRuleNode); - } + relations.add(sourceRuleNodeToTargetRuleNode); + } } } + if (!relations.isEmpty()) { + relationService.saveRelations(tenantId, relations); + } + return RuleChainUpdateResult.successful(updatedRuleNodes); } @@ -271,6 +285,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); ruleChainMetaData.setRuleChainId(ruleChainId); List ruleNodes = getRuleChainNodes(tenantId, ruleChainId); + Collections.sort(ruleNodes, Comparator.comparingLong(RuleNode::getCreatedTime).thenComparing(RuleNode::getId, Comparator.comparing(RuleNodeId::getId))); Map ruleNodeIndexMap = new HashMap<>(); for (RuleNode node : ruleNodes) { ruleNodeIndexMap.put(node.getId(), ruleNodes.indexOf(node)); @@ -293,6 +308,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } } } + if (ruleChainMetaData.getConnections() != null) { + Collections.sort(ruleChainMetaData.getConnections(), Comparator.comparingInt(NodeConnectionInfo::getFromIndex) + .thenComparing(NodeConnectionInfo::getToIndex).thenComparing(NodeConnectionInfo::getType)); + } return ruleChainMetaData; } @@ -390,6 +409,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return ruleChainDao.findRuleChainsByTenantIdAndType(tenantId.getId(), type, pageLink); } + @Override + public Collection findTenantRuleChainsByTypeAndName(TenantId tenantId, RuleChainType type, String name) { + return ruleChainDao.findByTenantIdAndTypeAndName(tenantId, type, name); + } + @Override @Transactional public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) { @@ -456,7 +480,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ruleChain.setRoot(false); if (overwrite) { - Collection existingRuleChains = ruleChainDao.findByTenantIdAndTypeAndName(tenantId, + Collection existingRuleChains = findTenantRuleChainsByTypeAndName(tenantId, Optional.ofNullable(ruleChain.getType()).orElse(RuleChainType.CORE), ruleChain.getName()); Optional existingRuleChain = existingRuleChains.stream().findFirst(); if (existingRuleChain.isPresent()) { @@ -547,6 +571,19 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } } } + if (!CollectionUtils.isEmpty(metaData.getNodes())) { + metaData.getNodes().stream() + .filter(ruleNode -> ruleNode.getType().equals(TB_RULE_CHAIN_INPUT_NODE)) + .forEach(ruleNode -> { + ObjectNode configuration = (ObjectNode) ruleNode.getConfiguration(); + if (configuration.has("ruleChainId")) { + if (configuration.get("ruleChainId").asText().equals(oldRuleChainId.toString())) { + configuration.put("ruleChainId", newRuleChainId.toString()); + ruleNode.setConfiguration(configuration); + } + } + }); + } } } @@ -699,6 +736,19 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC throw t; } } + deleteRuleNodes(tenantId, ruleChainId); + } + + private void deleteRuleNodes(TenantId tenantId, List ruleNodes) { + List ruleNodeIds = ruleNodes.stream().map(RuleNode::getId).collect(Collectors.toList()); + for (var node : ruleNodes) { + deleteEntityRelations(tenantId, node.getId()); + } + ruleNodeDao.deleteByIdIn(ruleNodeIds); + } + + @Override + public void deleteRuleNodes(TenantId tenantId, RuleChainId ruleChainId) { List nodeRelations = getRuleChainToNodeRelations(tenantId, ruleChainId); for (EntityRelation relation : nodeRelations) { deleteRuleNode(tenantId, relation.getTo()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index f53f8a9e9a..d19074aa53 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -15,12 +15,14 @@ */ package org.thingsboard.server.dao.rule; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.dao.Dao; +import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; import java.util.Collection; @@ -29,7 +31,7 @@ import java.util.UUID; /** * Created by igor on 3/12/18. */ -public interface RuleChainDao extends Dao, TenantEntityDao { +public interface RuleChainDao extends Dao, TenantEntityDao, ExportableEntityDao { /** * Find rule chains by tenantId and page link. diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java index b4f087b98a..4fb15e5721 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleNodeDao.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.rule; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -31,4 +33,8 @@ public interface RuleNodeDao extends Dao { List findRuleNodesByTenantIdAndType(TenantId tenantId, String type, String search); PageData findAllRuleNodesByType(String type, PageLink pageLink); + + List findByExternalIds(RuleChainId ruleChainId, List externalIds); + + void deleteByIdIn(List ruleNodeIds); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java index 1b27a2751f..81297b3594 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service; import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; @@ -36,6 +37,11 @@ public abstract class DataValidator> { private static final Pattern EMAIL_PATTERN = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$", Pattern.CASE_INSENSITIVE); + private static final Pattern QUEUE_PATTERN = Pattern.compile("^[a-zA-Z0-9_.\\-]+$"); + + private static final String NAME = "name"; + private static final String TOPIC = "topic"; + // Returns old instance of the same object that is fetched during validation. public D validate(D data, Function tenantIdFunction) { try { @@ -134,4 +140,22 @@ public abstract class DataValidator> { } } + protected static void validateQueueName(String name) { + validateQueueNameOrTopic(name, NAME); + } + + protected static void validateQueueTopic(String topic) { + validateQueueNameOrTopic(topic, TOPIC); + } + + private static void validateQueueNameOrTopic(String value, String fieldName) { + if (StringUtils.isEmpty(value)) { + throw new DataValidationException(String.format("Queue %s should be specified!", fieldName)); + } + if (!QUEUE_PATTERN.matcher(value).matches()) { + throw new DataValidationException( + String.format("Queue %s contains a character other than ASCII alphanumerics, '.', '_' and '-'!", fieldName)); + } + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AbstractHasOtaPackageValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AbstractHasOtaPackageValidator.java new file mode 100644 index 0000000000..069e1fcfc8 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AbstractHasOtaPackageValidator.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.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasOtaPackage; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.service.DataValidator; + +public abstract class AbstractHasOtaPackageValidator> extends DataValidator { + + @Autowired + @Lazy + private OtaPackageService otaPackageService; + + protected void validateOtaPackage(TenantId tenantId, T entity, DeviceProfileId deviceProfileId) { + if (entity.getFirmwareId() != null) { + OtaPackage firmware = otaPackageService.findOtaPackageById(tenantId, entity.getFirmwareId()); + validateOtaPackage(tenantId, OtaPackageType.FIRMWARE, deviceProfileId, firmware); + } + if (entity.getSoftwareId() != null) { + OtaPackage software = otaPackageService.findOtaPackageById(tenantId, entity.getSoftwareId()); + validateOtaPackage(tenantId, OtaPackageType.SOFTWARE, deviceProfileId, software); + } + } + + private void validateOtaPackage(TenantId tenantId, OtaPackageType type, DeviceProfileId deviceProfileId, OtaPackage otaPackage) { + if (otaPackage == null) { + throw new DataValidationException(prepareMsg("Can't assign non-existent %s!", type)); + } + if (!otaPackage.getTenantId().equals(tenantId)) { + throw new DataValidationException(prepareMsg("Can't assign %s from different tenant!", type)); + } + if (!otaPackage.getType().equals(type)) { + throw new DataValidationException(prepareMsg("Can't assign %s with type: " + otaPackage.getType(), type)); + } + if (otaPackage.getData() == null && !otaPackage.hasUrl()) { + throw new DataValidationException(prepareMsg("Can't assign %s with empty data!", type)); + } + if (!otaPackage.getDeviceProfileId().equals(deviceProfileId)) { + throw new DataValidationException(prepareMsg("Can't assign %s with different deviceProfile!", type)); + } + } + + private String prepareMsg(String msg, OtaPackageType type) { + return String.format(msg, type.name().toLowerCase()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java index 5bbe636dc7..a95f7a43aa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AlarmDataValidator.java @@ -18,18 +18,17 @@ 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; +import org.thingsboard.server.dao.tenant.TenantService; @Component @AllArgsConstructor public class AlarmDataValidator extends DataValidator { - private final TenantDao tenantDao; + private final TenantService tenantService; @Override protected void validateDataImpl(TenantId tenantId, Alarm alarm) { @@ -48,8 +47,7 @@ public class AlarmDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(alarm.getTenantId())) { throw new DataValidationException("Alarm is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java index a89acb9631..28e4dc0582 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ApiUsageDataValidator.java @@ -15,29 +15,29 @@ */ package org.thingsboard.server.dao.service.validator; -import lombok.AllArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; 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; +import org.thingsboard.server.dao.tenant.TenantService; @Component -@AllArgsConstructor public class ApiUsageDataValidator extends DataValidator { - private final TenantDao tenantDao; + @Lazy + @Autowired + private TenantService tenantService; @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)) { + if (!tenantService.tenantExists(apiUsageState.getTenantId()) && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) { throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java index a2204491f9..1f303cc79d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/AssetDataValidator.java @@ -21,7 +21,6 @@ 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; @@ -32,7 +31,7 @@ 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 org.thingsboard.server.dao.tenant.TenantService; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -43,7 +42,7 @@ public class AssetDataValidator extends DataValidator { private AssetDao assetDao; @Autowired - private TenantDao tenantDao; + private TenantService tenantService; @Autowired private CustomerDao customerDao; @@ -82,8 +81,7 @@ public class AssetDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(asset.getTenantId())) { throw new DataValidationException("Asset is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java index 8184ff8e8e..f0c7c3e15d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/BaseOtaPackageDataValidator.java @@ -16,22 +16,23 @@ package org.thingsboard.server.dao.service.validator; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; 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 org.thingsboard.server.dao.tenant.TenantService; import java.util.Objects; public abstract class BaseOtaPackageDataValidator> extends DataValidator { @Autowired - private TenantDao tenantDao; + @Lazy + private TenantService tenantService; @Autowired private DeviceProfileDao deviceProfileDao; @@ -40,8 +41,7 @@ public abstract class BaseOtaPackageDataValidator> extends 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) { + if (!tenantService.tenantExists(otaPackageInfo.getTenantId())) { throw new DataValidationException("OtaPackage is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java index b8e1067887..9cc8d6a0de 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/CustomerDataValidator.java @@ -21,7 +21,6 @@ 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; @@ -29,7 +28,7 @@ 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; +import org.thingsboard.server.dao.tenant.TenantService; import java.util.Optional; @@ -40,7 +39,7 @@ public class CustomerDataValidator extends DataValidator { private CustomerDao customerDao; @Autowired - private TenantDao tenantDao; + private TenantService tenantService; @Autowired @Lazy @@ -87,8 +86,7 @@ public class CustomerDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(customer.getTenantId())) { throw new DataValidationException("Customer is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java index ee226af853..23b960253c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java @@ -21,14 +21,13 @@ 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; +import org.thingsboard.server.dao.tenant.TenantService; @Component public class DashboardDataValidator extends DataValidator { @@ -37,7 +36,7 @@ public class DashboardDataValidator extends DataValidator { private DashboardDao dashboardDao; @Autowired - private TenantDao tenantDao; + private TenantService tenantService; @Autowired @Lazy @@ -59,8 +58,7 @@ public class DashboardDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(dashboard.getTenantId())) { throw new DataValidationException("Dashboard is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java index e9d8b82e1c..d30180a892 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceDataValidator.java @@ -22,33 +22,28 @@ 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.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 org.thingsboard.server.dao.tenant.TenantService; import java.util.Optional; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @Component -public class DeviceDataValidator extends DataValidator { +public class DeviceDataValidator extends AbstractHasOtaPackageValidator { @Autowired private DeviceDao deviceDao; @Autowired - private TenantDao tenantDao; + private TenantService tenantService; @Autowired private CustomerDao customerDao; @@ -57,9 +52,6 @@ public class DeviceDataValidator extends DataValidator { @Lazy private TbTenantProfileCache tenantProfileCache; - @Autowired - private OtaPackageService otaPackageService; - @Override protected void validateCreate(TenantId tenantId, Device device) { DefaultTenantProfileConfiguration profileConfiguration = @@ -85,8 +77,7 @@ public class DeviceDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(device.getTenantId())) { throw new DataValidationException("Device is referencing to non-existent tenant!"); } } @@ -105,36 +96,6 @@ public class DeviceDataValidator extends DataValidator { .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!"); - } - } + validateOtaPackage(tenantId, device, device.getDeviceProfileId()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 6f94b706c6..8f6083feec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -26,9 +26,7 @@ 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.DynamicProtoUtils; -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; @@ -44,7 +42,6 @@ import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBo import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.msg.EncryptionUtil; @@ -54,19 +51,17 @@ import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; -import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; -import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantService; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; @Component -public class DeviceProfileDataValidator extends DataValidator { +public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator { private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; @@ -82,16 +77,16 @@ public class DeviceProfileDataValidator extends DataValidator { @Autowired private DeviceDao deviceDao; @Autowired - private TenantDao tenantDao; - @Autowired + private TenantService tenantService; @Lazy - private QueueService queueService; @Autowired - private OtaPackageService otaPackageService; + private QueueService queueService; @Autowired private RuleChainService ruleChainService; @Autowired private DashboardService dashboardService; + @Autowired + private TbTenantProfileCache tenantProfileCache; @Override protected void validateDataImpl(TenantId tenantId, DeviceProfile deviceProfile) { @@ -107,8 +102,7 @@ public class DeviceProfileDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(deviceProfile.getTenantId())) { throw new DataValidationException("Device profile is referencing to non-existent tenant!"); } } @@ -118,8 +112,8 @@ public class DeviceProfileDataValidator extends DataValidator { throw new DataValidationException("Another default device profile is present in scope of current tenant!"); } } - if (deviceProfile.getDefaultQueueId() != null) { - Queue queue = queueService.findQueueById(tenantId, deviceProfile.getDefaultQueueId()); + if (StringUtils.isNotEmpty(deviceProfile.getDefaultQueueName())) { + Queue queue = queueService.findQueueByTenantIdAndName(tenantId, deviceProfile.getDefaultQueueName()); if (queue == null) { throw new DataValidationException("Device profile is referencing to non-existent queue!"); } @@ -182,6 +176,9 @@ public class DeviceProfileDataValidator extends DataValidator { if (ruleChain == null) { throw new DataValidationException("Can't assign non-existent rule chain!"); } + if (!ruleChain.getTenantId().equals(deviceProfile.getTenantId())) { + throw new DataValidationException("Can't assign rule chain from different tenant!"); + } } if (deviceProfile.getDefaultDashboardId() != null) { @@ -189,39 +186,12 @@ public class DeviceProfileDataValidator extends DataValidator { 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 (!dashboard.getTenantId().equals(deviceProfile.getTenantId())) { + throw new DataValidationException("Can't assign dashboard from different tenant!"); } } - 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!"); - } - } + validateOtaPackage(tenantId, deviceProfile, deviceProfile.getId()); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java index 8289d21b23..b88724f7af 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EdgeDataValidator.java @@ -19,7 +19,6 @@ 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; @@ -27,7 +26,7 @@ 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 org.thingsboard.server.dao.tenant.TenantService; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -36,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; public class EdgeDataValidator extends DataValidator { private final EdgeDao edgeDao; - private final TenantDao tenantDao; + private final TenantService tenantService; private final CustomerDao customerDao; @Override @@ -65,8 +64,7 @@ public class EdgeDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(edge.getTenantId())) { throw new DataValidationException("Edge is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java index a38850c63c..7532dda195 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/EntityViewDataValidator.java @@ -20,14 +20,13 @@ 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 org.thingsboard.server.dao.tenant.TenantService; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; @@ -36,7 +35,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; public class EntityViewDataValidator extends DataValidator { private final EntityViewDao entityViewDao; - private final TenantDao tenantDao; + private final TenantService tenantService; private final CustomerDao customerDao; @Override @@ -69,8 +68,7 @@ public class EntityViewDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(entityView.getTenantId())) { throw new DataValidationException("Entity view is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueValidator.java index c8ce639d40..1eaa64f446 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/QueueValidator.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.dao.service.validator; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.TenantProfile; @@ -73,12 +72,9 @@ public class QueueValidator extends DataValidator { } } - if (StringUtils.isEmpty(queue.getName())) { - throw new DataValidationException("Queue name should be specified!"); - } - if (StringUtils.isBlank(queue.getTopic())) { - throw new DataValidationException("Queue topic should be non empty and without spaces!"); - } + validateQueueName(queue.getName()); + validateQueueTopic(queue.getTopic()); + if (queue.getPollInterval() < 1) { throw new DataValidationException("Queue poll interval should be more then 0!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java index 90ac54577a..49de43dba3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/ResourceDataValidator.java @@ -20,7 +20,6 @@ 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; @@ -28,7 +27,7 @@ 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 org.thingsboard.server.dao.tenant.TenantService; import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE; @@ -39,7 +38,7 @@ public class ResourceDataValidator extends DataValidator { private TbResourceDao resourceDao; @Autowired - private TenantDao tenantDao; + private TenantService tenantService; @Autowired @Lazy @@ -73,8 +72,7 @@ public class ResourceDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(resource.getTenantId())) { throw new DataValidationException("Resource is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java index f36b4d23fa..1581a61b80 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/RuleChainDataValidator.java @@ -20,7 +20,6 @@ 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; @@ -30,7 +29,7 @@ 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; +import org.thingsboard.server.dao.tenant.TenantService; @Component public class RuleChainDataValidator extends DataValidator { @@ -43,7 +42,7 @@ public class RuleChainDataValidator extends DataValidator { private RuleChainService ruleChainService; @Autowired - private TenantDao tenantDao; + private TenantService tenantService; @Autowired @Lazy @@ -68,8 +67,7 @@ public class RuleChainDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(ruleChain.getTenantId())) { throw new DataValidationException("Rule chain is referencing to non-existent tenant!"); } if (ruleChain.isRoot() && RuleChainType.CORE.equals(ruleChain.getType())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java index ce15ec9e88..8484ecfa59 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidator.java @@ -100,19 +100,14 @@ public class TenantProfileDataValidator extends DataValidator { 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!"); } return old; } private void validateQueueConfiguration(TenantProfileQueueConfiguration queue) { - if (StringUtils.isEmpty(queue.getName())) { - throw new DataValidationException("Queue name should be specified!"); - } - if (StringUtils.isBlank(queue.getTopic())) { - throw new DataValidationException("Queue topic should be non empty and without spaces!"); - } + validateQueueName(queue.getName()); + validateQueueTopic(queue.getTopic()); + if (queue.getPollInterval() < 1) { throw new DataValidationException("Queue poll interval should be more then 0!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java index 94b894acce..21e2143f1a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java @@ -21,7 +21,6 @@ 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; @@ -32,7 +31,7 @@ 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.tenant.TenantService; import org.thingsboard.server.dao.user.UserDao; import org.thingsboard.server.dao.user.UserService; @@ -46,9 +45,6 @@ public class UserDataValidator extends DataValidator { @Lazy private UserService userService; - @Autowired - private TenantDao tenantDao; - @Autowired private CustomerDao customerDao; @@ -56,6 +52,10 @@ public class UserDataValidator extends DataValidator { @Lazy private TbTenantProfileCache tenantProfileCache; + @Autowired + @Lazy + private TenantService tenantService; + @Override protected void validateCreate(TenantId tenantId, User user) { if (!user.getTenantId().getId().equals(ModelConstants.NULL_UUID)) { @@ -119,8 +119,7 @@ public class UserDataValidator extends DataValidator { + " already present in database!"); } if (!tenantId.getId().equals(ModelConstants.NULL_UUID)) { - Tenant tenant = tenantDao.findById(tenantId, user.getTenantId().getId()); - if (tenant == null) { + if (!tenantService.tenantExists(user.getTenantId())) { throw new DataValidationException("User is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java index 93a1766546..25c6b5db86 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java @@ -27,6 +27,7 @@ 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.tenant.TenantService; import org.thingsboard.server.dao.widget.WidgetTypeDao; import org.thingsboard.server.dao.widget.WidgetsBundleDao; @@ -35,8 +36,8 @@ import org.thingsboard.server.dao.widget.WidgetsBundleDao; public class WidgetTypeDataValidator extends DataValidator { private final WidgetTypeDao widgetTypeDao; - private final TenantDao tenantDao; private final WidgetsBundleDao widgetsBundleDao; + private final TenantService tenantService; @Override protected void validateDataImpl(TenantId tenantId, WidgetTypeDetails widgetTypeDetails) { @@ -53,8 +54,7 @@ public class WidgetTypeDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(widgetTypeDetails.getTenantId())) { throw new DataValidationException("Widget type is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java index b140dfc79d..b950a12826 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetsBundleDataValidator.java @@ -18,13 +18,12 @@ 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.tenant.TenantService; import org.thingsboard.server.dao.widget.WidgetsBundleDao; @Component @@ -32,7 +31,7 @@ import org.thingsboard.server.dao.widget.WidgetsBundleDao; public class WidgetsBundleDataValidator extends DataValidator { private final WidgetsBundleDao widgetsBundleDao; - private final TenantDao tenantDao; + private final TenantService tenantService; @Override protected void validateDataImpl(TenantId tenantId, WidgetsBundle widgetsBundle) { @@ -43,8 +42,7 @@ public class WidgetsBundleDataValidator extends DataValidator { 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) { + if (!tenantService.tenantExists(widgetsBundle.getTenantId())) { throw new DataValidationException("Widgets bundle is referencing to non-existent tenant!"); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java index faea2cc34e..972d580cd7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java @@ -19,6 +19,8 @@ import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.Dao; +import java.util.UUID; + public interface AdminSettingsDao extends Dao { /** @@ -35,6 +37,10 @@ public interface AdminSettingsDao extends Dao { * @param key the key * @return the admin settings object */ - AdminSettings findByKey(TenantId tenantId, String key); + AdminSettings findByTenantIdAndKey(UUID tenantId, String key); + + boolean removeByTenantIdAndKey(UUID tenantId, String key); + + void removeByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java index bf4c71d296..28b4c97876 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.TenantId; @@ -46,21 +47,40 @@ public class AdminSettingsServiceImpl implements AdminSettingsService { public AdminSettings findAdminSettingsByKey(TenantId tenantId, String key) { log.trace("Executing findAdminSettingsByKey [{}]", key); Validator.validateString(key, "Incorrect key " + key); - return adminSettingsDao.findByKey(tenantId, key); + return findAdminSettingsByTenantIdAndKey(TenantId.SYS_TENANT_ID, key); + } + + @Override + public AdminSettings findAdminSettingsByTenantIdAndKey(TenantId tenantId, String key) { + return adminSettingsDao.findByTenantIdAndKey(tenantId.getId(), key); } @Override public AdminSettings saveAdminSettings(TenantId tenantId, AdminSettings adminSettings) { log.trace("Executing saveAdminSettings [{}]", adminSettings); adminSettingsValidator.validate(adminSettings, data -> tenantId); - if(adminSettings.getKey().equals("mail") && !adminSettings.getJsonValue().has("password")) { + if (adminSettings.getKey().equals("mail") && !adminSettings.getJsonValue().has("password")) { AdminSettings mailSettings = findAdminSettingsByKey(tenantId, "mail"); if (mailSettings != null) { ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText()); } } - + if (adminSettings.getTenantId() == null) { + adminSettings.setTenantId(TenantId.SYS_TENANT_ID); + } return adminSettingsDao.save(tenantId, adminSettings); } + @Override + public boolean deleteAdminSettingsByTenantIdAndKey(TenantId tenantId, String key) { + log.trace("Executing deleteAdminSettings, tenantId [{}], key [{}]", tenantId, key); + Validator.validateString(key, "Incorrect key " + key); + return adminSettingsDao.removeByTenantIdAndKey(tenantId.getId(), key); + } + + @Override + public void deleteAdminSettingsByTenantId(TenantId tenantId) { + adminSettingsDao.removeByTenantId(tenantId.getId()); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java index 3c996ff6f5..ad6bb5ccec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java @@ -67,6 +67,14 @@ public abstract class JpaAbstractDao, D> return DaoUtil.getData(entity); } + @Override + @Transactional + public D saveAndFlush(TenantId tenantId, D domain) { + D d = save(tenantId, domain); + getRepository().flush(); + return d; + } + @Override public D findById(TenantId tenantId, UUID key) { log.debug("Get entity by key {}", key); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java index 43d43a5f8e..0b409d9256 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/EntityAlarmRepository.java @@ -16,6 +16,9 @@ package org.thingsboard.server.dao.sql.alarm; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.EntityAlarmCompositeKey; import org.thingsboard.server.dao.model.sql.EntityAlarmEntity; @@ -28,5 +31,7 @@ public interface EntityAlarmRepository extends JpaRepository findAllByAlarmId(UUID alarmId); @Transactional - void deleteByEntityId(UUID id); + @Modifying + @Query("DELETE FROM EntityAlarmEntity e where e.entityId = :entityId") + void deleteByEntityId(@Param("entityId") UUID entityId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index cdf04f47c5..8e165898a2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; @@ -188,4 +189,10 @@ public class JpaAlarmDao extends JpaAbstractDao implements A log.trace("[{}] Try to delete entity alarm records using [{}]", tenantId, entityId); entityAlarmRepository.deleteByEntityId(entityId.getId()); } + + @Override + public EntityType getEntityType() { + return EntityType.ALARM; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index 833fcc2de8..6d4667d8ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.AssetEntity; import org.thingsboard.server.dao.model.sql.AssetInfoEntity; @@ -29,7 +30,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 5/21/2017. */ -public interface AssetRepository extends JpaRepository { +public interface AssetRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo) " + "FROM AssetEntity a " + @@ -143,4 +144,8 @@ public interface AssetRepository extends JpaRepository { Pageable pageable); Long countByTenantIdAndTypeIsNot(UUID tenantId, String type); + + @Query("SELECT externalId FROM AssetEntity WHERE id = :id") + UUID getExternalIdById(@Param("id") UUID id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index c9752c5390..b18d6c5f51 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; +import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -208,4 +209,31 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im public Long countByTenantId(TenantId tenantId) { return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE); } + + @Override + public Asset findByTenantIdAndExternalId(UUID tenantId, UUID externalId) { + return DaoUtil.getData(assetRepository.findByTenantIdAndExternalId(tenantId, externalId)); + } + + @Override + public Asset findByTenantIdAndName(UUID tenantId, String name) { + return findAssetsByTenantIdAndName(tenantId, name).orElse(null); + } + + @Override + public PageData findByTenantId(UUID tenantId, PageLink pageLink) { + return findAssetsByTenantId(tenantId, pageLink); + } + + @Override + public AssetId getExternalIdByInternal(AssetId internalId) { + return Optional.ofNullable(assetRepository.getExternalIdById(internalId.getId())) + .map(AssetId::new).orElse(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.ASSET; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java index 1a549436d0..4ec4a562c5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/component/ComponentDescriptorRepository.java @@ -18,8 +18,10 @@ package org.thingsboard.server.dao.sql.component; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.plugin.ComponentScope; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.dao.model.sql.ComponentDescriptorEntity; @@ -46,5 +48,8 @@ public interface ComponentDescriptorRepository extends JpaRepository { +public interface CustomerRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT c FROM CustomerEntity c WHERE c.tenantId = :tenantId " + "AND LOWER(c.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") @@ -38,4 +39,8 @@ public interface CustomerRepository extends JpaRepository CustomerEntity findByTenantIdAndTitle(UUID tenantId, String title); Long countByTenantId(UUID tenantId); + + @Query("SELECT externalId FROM CustomerEntity WHERE id = :id") + UUID getExternalIdById(@Param("id") UUID id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java index 02311df61c..ab3a4b4f8d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/customer/JpaCustomerDao.java @@ -19,6 +19,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -68,4 +70,31 @@ public class JpaCustomerDao extends JpaAbstractSearchTextDao findByTenantId(UUID tenantId, PageLink pageLink) { + return findCustomersByTenantId(tenantId, pageLink); + } + + @Override + public CustomerId getExternalIdByInternal(CustomerId internalId) { + return Optional.ofNullable(customerRepository.getExternalIdById(internalId.getId())) + .map(CustomerId::new).orElse(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.CUSTOMER; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java index 36518887d4..eaa247b96d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardRepository.java @@ -15,15 +15,29 @@ */ package org.thingsboard.server.dao.sql.dashboard; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.DashboardEntity; +import java.util.List; import java.util.UUID; /** * Created by Valerii Sosliuk on 5/6/2017. */ -public interface DashboardRepository extends JpaRepository { +public interface DashboardRepository extends JpaRepository, ExportableEntityRepository { Long countByTenantId(UUID tenantId); + + List findByTenantIdAndTitle(UUID tenantId, String title); + + Page findByTenantId(UUID tenantId, Pageable pageable); + + @Query("SELECT externalId FROM DashboardEntity WHERE id = :id") + UUID getExternalIdById(@Param("id") UUID id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java index 98edcaa1fd..425c54c647 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardDao.java @@ -19,11 +19,18 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.dashboard.DashboardDao; import org.thingsboard.server.dao.model.sql.DashboardEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; +import java.util.List; +import java.util.Optional; import java.util.UUID; /** @@ -49,4 +56,31 @@ public class JpaDashboardDao extends JpaAbstractSearchTextDao findByTenantId(UUID tenantId, PageLink pageLink) { + return DaoUtil.toPageData(dashboardRepository.findByTenantId(tenantId, DaoUtil.toPageable(pageLink))); + } + + @Override + public DashboardId getExternalIdByInternal(DashboardId internalId) { + return Optional.ofNullable(dashboardRepository.getExternalIdById(internalId.getId())) + .map(DashboardId::new).orElse(null); + } + + @Override + public List findByTenantIdAndTitle(UUID tenantId, String title) { + return DaoUtil.convertDataList(dashboardRepository.findByTenantIdAndTitle(tenantId, title)); + } + + @Override + public EntityType getEntityType() { + return EntityType.DASHBOARD; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index e61a60b5b6..fea0e4bfc0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -22,11 +22,12 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import java.util.UUID; -public interface DeviceProfileRepository extends JpaRepository { +public interface DeviceProfileRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.defaultDashboardId, d.type, d.transportType) " + "FROM DeviceProfileEntity d " + @@ -66,4 +67,8 @@ public interface DeviceProfileRepository extends JpaRepository { +public interface DeviceRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + @@ -244,4 +245,8 @@ public interface DeviceRepository extends JpaRepository { "INNER JOIN DeviceProfileEntity p ON d.deviceProfileId = p.id " + "WHERE p.transportType = :transportType") Page findIdsByDeviceProfileTransportType(@Param("transportType") DeviceTransportType transportType, Pageable pageable); + + @Query("SELECT externalId FROM DeviceEntity WHERE id = :id") + UUID getExternalIdById(@Param("id") UUID id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 1d75690e50..8513e440b7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageUtil; @@ -302,4 +303,31 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); } + + @Override + public Device findByTenantIdAndExternalId(UUID tenantId, UUID externalId) { + return DaoUtil.getData(deviceRepository.findByTenantIdAndExternalId(tenantId, externalId)); + } + + @Override + public Device findByTenantIdAndName(UUID tenantId, String name) { + return findDeviceByTenantIdAndName(tenantId, name).orElse(null); + } + + @Override + public PageData findByTenantId(UUID tenantId, PageLink pageLink) { + return findDevicesByTenantId(tenantId, pageLink); + } + + @Override + public DeviceId getExternalIdByInternal(DeviceId internalId) { + return Optional.ofNullable(deviceRepository.getExternalIdById(internalId.getId())) + .map(DeviceId::new).orElse(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.DEVICE; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java index 80513d77c6..07a680493f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceProfileDao.java @@ -23,6 +23,8 @@ import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -32,6 +34,7 @@ import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.Objects; +import java.util.Optional; import java.util.UUID; @Component @@ -109,4 +112,31 @@ public class JpaDeviceProfileDao extends JpaAbstractSearchTextDao findByTenantId(UUID tenantId, PageLink pageLink) { + return findDeviceProfiles(TenantId.fromUUID(tenantId), pageLink); + } + + @Override + public DeviceProfileId getExternalIdByInternal(DeviceProfileId internalId) { + return Optional.ofNullable(deviceProfileRepository.getExternalIdById(internalId.getId())) + .map(DeviceProfileId::new).orElse(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.DEVICE_PROFILE; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java index 5ecb60e744..36f834b8bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java @@ -72,9 +72,6 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao queue; @Autowired @@ -110,7 +107,7 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao(params, hashcodeFunction, batchThreads, statsFactory); + queue = new TbSqlBlockingQueueWrapper<>(params, hashcodeFunction, 1, statsFactory); queue.init(logExecutor, v -> edgeEventInsertRepository.save(v), Comparator.comparing(EdgeEventEntity::getTs) ); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java index 4be9c08849..802c9b99b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java @@ -193,4 +193,9 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao imple return list; } + @Override + public EntityType getEntityType() { + return EntityType.EDGE; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java index 7a88aa3c5b..0999523c90 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/entityview/EntityViewRepository.java @@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.EntityViewEntity; import org.thingsboard.server.dao.model.sql.EntityViewInfoEntity; @@ -29,7 +30,7 @@ import java.util.UUID; /** * Created by Victor Basanets on 8/31/2017. */ -public interface EntityViewRepository extends JpaRepository { +public interface EntityViewRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT new org.thingsboard.server.dao.model.sql.EntityViewInfoEntity(e, c.title, c.additionalInfo) " + "FROM EntityViewEntity e " + @@ -139,4 +140,7 @@ public interface EntityViewRepository extends JpaRepository findByTenantId(UUID tenantId, PageLink pageLink) { + return findEntityViewsByTenantId(tenantId, pageLink); + } + + @Override + public EntityViewId getExternalIdByInternal(EntityViewId internalId) { + return Optional.ofNullable(entityViewRepository.getExternalIdById(internalId.getId())) + .map(EntityViewId::new).orElse(null); + } + + @Override + public EntityView findByTenantIdAndName(UUID tenantId, String name) { + return findEntityViewByTenantIdAndName(tenantId, name).orElse(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.ENTITY_VIEW; + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java index 53c1318948..6af11346fd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.sql.OtaPackageEntity; @@ -48,4 +49,10 @@ public class JpaOtaPackageDao extends JpaAbstractSearchTextDao(widgetEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields); + allowedEntityFieldMap.put(EntityType.DEVICE_PROFILE, Set.of(CREATED_TIME, NAME, TYPE)); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java deleted file mode 100644 index ec6df53ae2..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/AbstractRelationInsertRepository.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.sql.relation; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.jpa.repository.Modifying; -import org.thingsboard.server.dao.model.sql.RelationEntity; - -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; -import javax.persistence.Query; - -@Slf4j -public abstract class AbstractRelationInsertRepository implements RelationInsertRepository { - - @PersistenceContext - protected EntityManager entityManager; - - protected Query getQuery(RelationEntity entity, String query) { - Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class); - if (entity.getAdditionalInfo() == null) { - nativeQuery.setParameter("additionalInfo", null); - } else { - nativeQuery.setParameter("additionalInfo", entity.getAdditionalInfo().toString()); - } - return nativeQuery - .setParameter("fromId", entity.getFromId()) - .setParameter("fromType", entity.getFromType()) - .setParameter("toId", entity.getToId()) - .setParameter("toType", entity.getToType()) - .setParameter("relationTypeGroup", entity.getRelationTypeGroup()) - .setParameter("relationType", entity.getRelationType()); - } - - @Modifying - protected abstract RelationEntity processSaveOrUpdate(RelationEntity entity); - -} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index 71e36fcae1..422e0a8623 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -33,7 +33,11 @@ import org.thingsboard.server.dao.model.sql.RelationEntity; import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; /** * Created by Valerii Sosliuk on 5/29/2017. @@ -42,6 +46,12 @@ import java.util.List; @Component public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService implements RelationDao { + private static final List ALL_TYPE_GROUP_NAMES = new ArrayList<>(); + + static { + Arrays.stream(RelationTypeGroup.values()).map(RelationTypeGroup::name).forEach(ALL_TYPE_GROUP_NAMES::add); + } + @Autowired private RelationRepository relationRepository; @@ -57,6 +67,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple typeGroup.name())); } + @Override + public List findAllByFrom(TenantId tenantId, EntityId from) { + return DaoUtil.convertDataList( + relationRepository.findAllByFromIdAndFromTypeAndRelationTypeGroupIn( + from.getId(), + from.getEntityType().name(), + ALL_TYPE_GROUP_NAMES)); + } + @Override public List findAllByFromAndType(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup) { return DaoUtil.convertDataList( @@ -76,6 +95,15 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple typeGroup.name())); } + @Override + public List findAllByTo(TenantId tenantId, EntityId to) { + return DaoUtil.convertDataList( + relationRepository.findAllByToIdAndToTypeAndRelationTypeGroupIn( + to.getId(), + to.getEntityType().name(), + ALL_TYPE_GROUP_NAMES)); + } + @Override public List findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup) { return DaoUtil.convertDataList( @@ -87,9 +115,14 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } @Override - public ListenableFuture checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { + public ListenableFuture checkRelationAsync(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { + return service.submit(() -> checkRelation(tenantId, from, to, relationType, typeGroup)); + } + + @Override + public boolean checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) { RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup); - return service.submit(() -> relationRepository.existsById(key)); + return relationRepository.existsById(key); } @Override @@ -112,6 +145,12 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple return relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null; } + @Override + public void saveRelations(TenantId tenantId, Collection relations) { + List entities = relations.stream().map(RelationEntity::new).collect(Collectors.toList()); + relationInsertRepository.saveOrUpdate(entities); + } + @Override public ListenableFuture saveRelationAsync(TenantId tenantId, EntityRelation relation) { return service.submit(() -> relationInsertRepository.saveOrUpdate(new RelationEntity(relation)) != null); @@ -156,19 +195,21 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } @Override - public boolean deleteOutboundRelations(TenantId tenantId, EntityId entity) { - boolean relationExistsBeforeDelete = false; + public void deleteOutboundRelations(TenantId tenantId, EntityId entity) { try { - relationExistsBeforeDelete = relationRepository - .findAllByFromIdAndFromType(entity.getId(), entity.getEntityType().name()) - .size() > 0; - if (relationExistsBeforeDelete) { - relationRepository.deleteByFromIdAndFromType(entity.getId(), entity.getEntityType().name()); - } + relationRepository.deleteByFromIdAndFromType(entity.getId(), entity.getEntityType().name()); + } catch (ConcurrencyFailureException e) { + log.debug("Concurrency exception while deleting relations [{}]", entity, e); + } + } + + @Override + public void deleteInboundRelations(TenantId tenantId, EntityId entity) { + try { + relationRepository.deleteByToIdAndToTypeAndRelationTypeGroupIn(entity.getId(), entity.getEntityType().name(), ALL_TYPE_GROUP_NAMES); } catch (ConcurrencyFailureException e) { log.debug("Concurrency exception while deleting relations [{}]", entity, e); } - return relationExistsBeforeDelete; } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java index a07b11bcda..527a7f2bcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationInsertRepository.java @@ -17,8 +17,12 @@ package org.thingsboard.server.dao.sql.relation; import org.thingsboard.server.dao.model.sql.RelationEntity; +import java.util.List; + public interface RelationInsertRepository { RelationEntity saveOrUpdate(RelationEntity entity); + void saveOrUpdate(List entities); + } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java index bddd344672..d71077c55e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.relation; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; @@ -35,6 +36,10 @@ public interface RelationRepository String fromType, String relationTypeGroup); + List findAllByFromIdAndFromTypeAndRelationTypeGroupIn(UUID fromId, + String fromType, + List relationTypeGroups); + List findAllByFromIdAndFromTypeAndRelationTypeAndRelationTypeGroup(UUID fromId, String fromType, String relationType, @@ -44,6 +49,10 @@ public interface RelationRepository String toType, String relationTypeGroup); + List findAllByToIdAndToTypeAndRelationTypeGroupIn(UUID toId, + String toType, + List relationTypeGroups); + List findAllByToIdAndToTypeAndRelationTypeAndRelationTypeGroup(UUID toId, String toType, String relationType, @@ -64,6 +73,13 @@ public interface RelationRepository void deleteById(RelationCompositeKey id); @Transactional - void deleteByFromIdAndFromType(UUID fromId, String fromType); + @Modifying + @Query("DELETE FROM RelationEntity r where r.fromId = :fromId and r.fromType = :fromType") + void deleteByFromIdAndFromType(@Param("fromId") UUID fromId, @Param("fromType") String fromType); + + @Transactional + @Modifying + @Query("DELETE FROM RelationEntity r where r.toId = :toId and r.toType = :toType and r.relationTypeGroup in :relationTypeGroups") + void deleteByToIdAndToTypeAndRelationTypeGroupIn(@Param("toId") UUID toId, @Param("toType") String toType, @Param("relationTypeGroups") List relationTypeGroups); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java index 23d24588b9..cbc4ca1e02 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/SqlRelationInsertRepository.java @@ -15,25 +15,90 @@ */ package org.thingsboard.server.dao.sql.relation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.BatchPreparedStatementSetter; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.dao.model.sql.RelationEntity; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.Query; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; + @Repository @Transactional -public class SqlRelationInsertRepository extends AbstractRelationInsertRepository implements RelationInsertRepository { +public class SqlRelationInsertRepository implements RelationInsertRepository { - private static final String INSERT_ON_CONFLICT_DO_UPDATE = "INSERT INTO relation (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" + + private static final String INSERT_ON_CONFLICT_DO_UPDATE_JPA = "INSERT INTO relation (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" + " VALUES (:fromId, :fromType, :toId, :toType, :relationTypeGroup, :relationType, :additionalInfo) " + "ON CONFLICT (from_id, from_type, relation_type_group, relation_type, to_id, to_type) DO UPDATE SET additional_info = :additionalInfo returning *"; + private static final String INSERT_ON_CONFLICT_DO_UPDATE_JDBC = "INSERT INTO relation (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" + + " VALUES (?, ?, ?, ?, ?, ?, ?) " + + "ON CONFLICT (from_id, from_type, relation_type_group, relation_type, to_id, to_type) DO UPDATE SET additional_info = ?"; + + + @PersistenceContext + protected EntityManager entityManager; + + @Autowired + protected JdbcTemplate jdbcTemplate; + + protected Query getQuery(RelationEntity entity, String query) { + Query nativeQuery = entityManager.createNativeQuery(query, RelationEntity.class); + if (entity.getAdditionalInfo() == null) { + nativeQuery.setParameter("additionalInfo", null); + } else { + nativeQuery.setParameter("additionalInfo", JacksonUtil.toString(entity.getAdditionalInfo())); + } + return nativeQuery + .setParameter("fromId", entity.getFromId()) + .setParameter("fromType", entity.getFromType()) + .setParameter("toId", entity.getToId()) + .setParameter("toType", entity.getToType()) + .setParameter("relationTypeGroup", entity.getRelationTypeGroup()) + .setParameter("relationType", entity.getRelationType()); + } + @Override public RelationEntity saveOrUpdate(RelationEntity entity) { - return processSaveOrUpdate(entity); + return (RelationEntity) getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE_JPA).getSingleResult(); } @Override - protected RelationEntity processSaveOrUpdate(RelationEntity entity) { - return (RelationEntity) getQuery(entity, INSERT_ON_CONFLICT_DO_UPDATE).getSingleResult(); + public void saveOrUpdate(List entities) { + jdbcTemplate.batchUpdate(INSERT_ON_CONFLICT_DO_UPDATE_JDBC, new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement ps, int i) throws SQLException { + RelationEntity relation = entities.get(i); + ps.setObject(1, relation.getFromId()); + ps.setString(2, relation.getFromType()); + ps.setObject(3, relation.getToId()); + ps.setString(4, relation.getToType()); + + ps.setString(5, relation.getRelationTypeGroup()); + ps.setString(6, relation.getRelationType()); + + if (relation.getAdditionalInfo() == null) { + ps.setString(7, null); + ps.setString(8, null); + } else { + String json = JacksonUtil.toString(relation.getAdditionalInfo()); + ps.setString(7, json); + ps.setString(8, json); + } + } + + @Override + public int getBatchSize() { + return entities.size(); + } + }); } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java index dbf60af096..5e4914f470 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.sql.resource; import lombok.extern.slf4j.Slf4j; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.TenantId; @@ -96,4 +97,10 @@ public class JpaTbResourceDao extends JpaAbstractSearchTextDao implements RpcDao public Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime) { return rpcRepository.deleteOutdatedRpcByTenantId(tenantId.getId(), expirationTime); } + + @Override + public EntityType getEntityType() { + return EntityType.RPC; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 3a7fc921e5..9ec2bab280 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -19,6 +19,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -31,6 +33,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.Collection; import java.util.Objects; +import java.util.Optional; import java.util.UUID; @Slf4j @@ -108,4 +111,25 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findByTenantId(UUID tenantId, PageLink pageLink) { + return findRuleChainsByTenantId(tenantId, pageLink); + } + + @Override + public RuleChainId getExternalIdByInternal(RuleChainId internalId) { + return Optional.ofNullable(ruleChainRepository.getExternalIdById(internalId.getId())) + .map(RuleChainId::new).orElse(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.RULE_CHAIN; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java index a0f84758c6..7fc1229cd3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleNodeDao.java @@ -19,6 +19,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -31,6 +34,7 @@ import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import java.util.List; import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; @Slf4j @Component @@ -63,4 +67,20 @@ public class JpaRuleNodeDao extends JpaAbstractSearchTextDao findByExternalIds(RuleChainId ruleChainId, List externalIds) { + return DaoUtil.convertDataList(ruleNodeRepository.findRuleNodesByRuleChainIdAndExternalIdIn(ruleChainId.getId(), + externalIds.stream().map(RuleNodeId::getId).collect(Collectors.toList()))); + } + + @Override + public void deleteByIdIn(List ruleNodeIds) { + ruleNodeRepository.deleteAllById(ruleNodeIds.stream().map(RuleNodeId::getId).collect(Collectors.toList())); + } + + @Override + public EntityType getEntityType() { + return EntityType.RULE_NODE; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index fe9be8dcc7..7c4b4b86f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -21,12 +21,13 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.RuleChainEntity; import java.util.List; import java.util.UUID; -public interface RuleChainRepository extends JpaRepository { +public interface RuleChainRepository extends JpaRepository, ExportableEntityRepository { @Query("SELECT rc FROM RuleChainEntity rc WHERE rc.tenantId = :tenantId " + "AND LOWER(rc.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") @@ -66,4 +67,7 @@ public interface RuleChainRepository extends JpaRepository findByTenantIdAndTypeAndName(UUID tenantId, RuleChainType type, String name); + @Query("SELECT externalId FROM RuleChainEntity WHERE id = :id") + UUID getExternalIdById(@Param("id") UUID id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java index f584ff2b02..e6a08b910a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleNodeRepository.java @@ -18,8 +18,10 @@ package org.thingsboard.server.dao.sql.rule; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.RuleNodeEntity; import java.util.List; @@ -31,12 +33,19 @@ public interface RuleNodeRepository extends JpaRepository "(select id from RuleChainEntity rc WHERE rc.tenantId = :tenantId) " + "AND r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") List findRuleNodesByTenantIdAndType(@Param("tenantId") UUID tenantId, - @Param("ruleType") String ruleType, - @Param("searchText") String searchText); + @Param("ruleType") String ruleType, + @Param("searchText") String searchText); @Query("SELECT r FROM RuleNodeEntity r WHERE r.type = :ruleType AND LOWER(r.configuration) LIKE LOWER(CONCAT('%', :searchText, '%')) ") Page findAllRuleNodesByType(@Param("ruleType") String ruleType, @Param("searchText") String searchText, Pageable pageable); + List findRuleNodesByRuleChainIdAndExternalIdIn(UUID ruleChainId, List externalIds); + + @Transactional + @Modifying + @Query("DELETE FROM RuleNodeEntity e where e.id in :ids") + void deleteByIdIn(@Param("ids") List ids); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java index 58423645b1..e607517f52 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java @@ -25,5 +25,12 @@ import java.util.UUID; */ public interface AdminSettingsRepository extends JpaRepository { - AdminSettingsEntity findByKey(String key); + AdminSettingsEntity findByTenantIdAndKey(UUID tenantId, String key); + + void deleteByTenantIdAndKey(UUID tenantId, String key); + + void deleteByTenantId(UUID tenantId); + + boolean existsByTenantIdAndKey(UUID tenantId, String key); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java index 438274e3cc..bd815bc410 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.DaoUtil; @@ -46,7 +47,24 @@ public class JpaAdminSettingsDao extends JpaAbstractDao return DaoUtil.pageToPageData(tenantRepository.findTenantsIds(DaoUtil.toPageable(pageLink))).mapData(TenantId::fromUUID); } + @Override + public EntityType getEntityType() { + return EntityType.TENANT; + } + @Override public List findTenantIdsByTenantProfileId(TenantProfileId tenantProfileId) { return tenantRepository.findTenantIdsByTenantProfileId(tenantProfileId.getId()).stream() diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java index 7b060e0b43..8c123b12fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java @@ -42,5 +42,6 @@ public interface ApiUsageStateRepository extends JpaRepository imple public Long countByTenantId(TenantId tenantId) { return userRepository.countByTenantId(tenantId.getId()); } + + @Override + public EntityType getEntityType() { + return EntityType.USER; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserAuthSettingsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserAuthSettingsRepository.java index 38642a0161..c5a8763209 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserAuthSettingsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserAuthSettingsRepository.java @@ -16,6 +16,9 @@ package org.thingsboard.server.dao.sql.user; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.model.sql.UserAuthSettingsEntity; @@ -28,6 +31,8 @@ public interface UserAuthSettingsRepository extends JpaRepository findByTenantId(UUID tenantId, PageLink pageLink) { + return findTenantWidgetsBundlesByTenantId(tenantId, pageLink); + } + + @Override + public WidgetsBundleId getExternalIdByInternal(WidgetsBundleId internalId) { + return Optional.ofNullable(widgetsBundleRepository.getExternalIdById(internalId.getId())) + .map(WidgetsBundleId::new).orElse(null); + } + + @Override + public EntityType getEntityType() { + return EntityType.WIDGETS_BUNDLE; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java index 0de8d0220b..188b42f646 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetsBundleRepository.java @@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.WidgetsBundleEntity; import java.util.UUID; @@ -27,7 +28,7 @@ import java.util.UUID; /** * Created by Valerii Sosliuk on 4/23/2017. */ -public interface WidgetsBundleRepository extends JpaRepository { +public interface WidgetsBundleRepository extends JpaRepository, ExportableEntityRepository { WidgetsBundleEntity findWidgetsBundleByTenantIdAndAlias(UUID tenantId, String alias); @@ -49,4 +50,10 @@ public interface WidgetsBundleRepository extends JpaRepository>> futures = new ArrayList<>(); - while (stepTs < query.getEndTs()) { - long startTs = stepTs; - long endTs = stepTs + query.getInterval(); + long endPeriod = query.getEndTs(); + long startPeriod = query.getStartTs(); + long step = query.getInterval(); + while (startPeriod <= endPeriod) { + long startTs = startPeriod; + long endTs = Math.min(startPeriod + step, endPeriod + 1); long ts = startTs + (endTs - startTs) / 2; - futures.add(findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation())); - stepTs = endTs; + ListenableFuture> aggregateTsKvEntry = findAndAggregateAsync(entityId, query.getKey(), startTs, endTs, ts, query.getAggregation()); + futures.add(aggregateTsKvEntry); + startPeriod = endTs; } return getTskvEntriesFuture(Futures.allAsList(futures)); } @@ -148,7 +152,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq return Futures.immediateFuture(DaoUtil.convertDataList(tsKvEntities)); } - private ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { + ListenableFuture> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) { List> entitiesFutures = new ArrayList<>(); switchAggregation(entityId, key, startTs, endTs, aggregation, entitiesFutures); return Futures.transform(setFutures(entitiesFutures), entity -> { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index 6009f9babe..aa0d9b1c47 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -91,7 +91,7 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries .stream() .map(query -> findAllAsync(tenantId, entityId, query)) .collect(Collectors.toList()); - return Futures.transform(Futures.allAsList(futures), new Function>, List>() { + return Futures.transform(Futures.allAsList(futures), new Function<>() { @Nullable @Override public List apply(@Nullable List> results) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java index de97976714..31270bacc7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AggregationTimeseriesDao.java @@ -26,4 +26,4 @@ import java.util.List; public interface AggregationTimeseriesDao { ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query); -} +} \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java index 56fe4f0f6f..82e54168bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/BaseAbstractSqlTimeseriesDao.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataIntegrityViolationException; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionary; import org.thingsboard.server.dao.model.sqlts.dictionary.TsKvDictionaryCompositeKey; @@ -39,9 +40,7 @@ import java.util.stream.Collectors; public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeningExecutorService { private final ConcurrentMap tsKvDictionaryMap = new ConcurrentHashMap<>(); - protected static final ReentrantLock tsCreationLock = new ReentrantLock(); - @Autowired protected TsKvDictionaryRepository dictionaryRepository; @@ -61,7 +60,7 @@ public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeni TsKvDictionary saved = dictionaryRepository.save(tsKvDictionary); tsKvDictionaryMap.put(saved.getKey(), saved.getKeyId()); keyId = saved.getKeyId(); - } catch (ConstraintViolationException e) { + } catch (DataIntegrityViolationException | ConstraintViolationException e) { tsKvDictionaryOptional = dictionaryRepository.findById(new TsKvDictionaryCompositeKey(strKey)); TsKvDictionary dictionary = tsKvDictionaryOptional.orElseThrow(() -> new RuntimeException("Failed to get TsKvDictionary entity from DB!")); tsKvDictionaryMap.put(dictionary.getKey(), dictionary.getKeyId()); @@ -96,4 +95,5 @@ public abstract class BaseAbstractSqlTimeseriesDao extends JpaAbstractDaoListeni } }, service); } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCaffeineCache.java new file mode 100644 index 0000000000..8a0e24f2c5 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantCaffeineCache.java @@ -0,0 +1,34 @@ +/** + * 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.tenant; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("TenantCache") +public class TenantCaffeineCache extends CaffeineTbTransactionalCache { + + public TenantCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.TENANTS_CACHE); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantEvictEvent.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantEvictEvent.java new file mode 100644 index 0000000000..b11497898a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantEvictEvent.java @@ -0,0 +1,25 @@ +/** + * 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.tenant; + +import lombok.Data; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +public class TenantEvictEvent { + private final TenantId tenantId; + private final boolean invalidateExists; +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsCaffeineCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsCaffeineCache.java new file mode 100644 index 0000000000..579afd9a11 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsCaffeineCache.java @@ -0,0 +1,35 @@ +/** + * 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.tenant; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantInfo; +import org.thingsboard.server.common.data.id.TenantId; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("TenantExistsCache") +public class TenantExistsCaffeineCache extends CaffeineTbTransactionalCache { + + public TenantExistsCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.TENANTS_EXIST_CACHE); + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java new file mode 100644 index 0000000000..1b41379753 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantExistsRedisCache.java @@ -0,0 +1,35 @@ +/** + * 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.tenant; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; +import org.thingsboard.server.common.data.id.TenantId; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("TenantExistsCache") +public class TenantExistsRedisCache extends RedisTbTransactionalCache { + + public TenantExistsRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.TENANTS_EXIST_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java index da83473f44..94855b2567 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileRedisCache.java @@ -23,13 +23,13 @@ import org.thingsboard.server.cache.TBRedisCacheConfiguration; import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.cache.RedisTbTransactionalCache; -import org.thingsboard.server.cache.TbRedisSerializer; +import org.thingsboard.server.cache.TbFSTRedisSerializer; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @Service("TenantProfileCache") public class TenantProfileRedisCache extends RedisTbTransactionalCache { public TenantProfileRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { - super(CacheConstants.TENANT_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbRedisSerializer<>()); + super(CacheConstants.TENANT_PROFILE_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 71140140c7..8a2d3354d4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -153,7 +153,6 @@ public class TenantProfileServiceImpl extends AbstractCachedEntityService { + + public TenantRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.TENANTS_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 61c037e2d6..866150e679 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -18,7 +18,11 @@ package org.thingsboard.server.dao.tenant; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionalEventListener; +import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; @@ -31,8 +35,7 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.resource.ResourceService; @@ -41,6 +44,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -51,7 +55,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @Service @Slf4j -public class TenantServiceImpl extends AbstractEntityService implements TenantService { +public class TenantServiceImpl extends AbstractCachedEntityService implements TenantService { private static final String DEFAULT_TENANT_REGION = "Global"; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @@ -63,6 +67,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe private TenantProfileService tenantProfileService; @Autowired + @Lazy private UserService userService; @Autowired @@ -77,12 +82,10 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private DeviceProfileService deviceProfileService; + @Lazy @Autowired private ApiUsageStateService apiUsageStateService; - @Autowired - private EntityViewService entityViewService; - @Autowired private WidgetsBundleService widgetsBundleService; @@ -96,6 +99,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe private ResourceService resourceService; @Autowired + @Lazy private OtaPackageService otaPackageService; @Autowired @@ -104,14 +108,32 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private DataValidator tenantValidator; + @Lazy @Autowired private QueueService queueService; + @Autowired + private AdminSettingsService adminSettingsService; + + @Autowired + protected TbTransactionalCache existsTenantCache; + + @TransactionalEventListener(classes = TenantEvictEvent.class) + @Override + public void handleEvictEvent(TenantEvictEvent event) { + TenantId tenantId = event.getTenantId(); + cache.evict(tenantId); + if (event.isInvalidateExists()) { + existsTenantCache.evict(tenantId); + } + } + @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return tenantDao.findById(tenantId, tenantId.getId()); + + return cache.getAndPutInTransaction(tenantId, () -> tenantDao.findById(tenantId, tenantId.getId()), true); } @Override @@ -123,12 +145,13 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Override public ListenableFuture findTenantByIdAsync(TenantId callerId, TenantId tenantId) { - log.trace("Executing TenantIdAsync [{}]", tenantId); + log.trace("Executing findTenantByIdAsync [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); return tenantDao.findByIdAsync(callerId, tenantId.getId()); } @Override + @Transactional public Tenant saveTenant(Tenant tenant) { log.trace("Executing saveTenant [{}]", tenant); tenant.setRegion(DEFAULT_TENANT_REGION); @@ -137,7 +160,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe tenant.setTenantProfileId(tenantProfile.getId()); } tenantValidator.validate(tenant, Tenant::getId); + boolean create = tenant.getId() == null; Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); + publishEvictEvent(new TenantEvictEvent(savedTenant.getId(), create)); if (tenant.getId() == null) { deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); apiUsageStateService.createDefaultApiUsageState(savedTenant.getId(), null); @@ -149,13 +174,13 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe public void deleteTenant(TenantId tenantId) { log.trace("Executing deleteTenant [{}]", tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - customerService.deleteCustomersByTenantId(tenantId); - widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); entityViewService.deleteEntityViewsByTenantId(tenantId); + widgetsBundleService.deleteWidgetsBundlesByTenantId(tenantId); assetService.deleteAssetsByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId); deviceProfileService.deleteDeviceProfilesByTenantId(tenantId); dashboardService.deleteDashboardsByTenantId(tenantId); + customerService.deleteCustomersByTenantId(tenantId); edgeService.deleteEdgesByTenantId(tenantId); userService.deleteTenantAdmins(tenantId); ruleChainService.deleteRuleChainsByTenantId(tenantId); @@ -164,7 +189,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe otaPackageService.deleteOtaPackagesByTenantId(tenantId); rpcService.deleteAllRpcByTenantId(tenantId); queueService.deleteQueuesByTenantId(tenantId); + adminSettingsService.deleteAdminSettingsByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); + publishEvictEvent(new TenantEvictEvent(tenantId, true)); deleteEntityRelations(tenantId, tenantId); } @@ -194,17 +221,28 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID); } - private PaginatedRemover tenantsRemover = - new PaginatedRemover<>() { + @Override + public PageData findTenantsIds(PageLink pageLink) { + log.trace("Executing findTenantsIds"); + Validator.validatePageLink(pageLink); + return tenantDao.findTenantsIds(pageLink); + } + + @Override + public boolean tenantExists(TenantId tenantId) { + return existsTenantCache.getAndPutInTransaction(tenantId, () -> tenantDao.existsById(tenantId, tenantId.getId()), false); + } + + private PaginatedRemover tenantsRemover = new PaginatedRemover<>() { - @Override - protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { - return tenantDao.findTenants(tenantId, pageLink); - } + @Override + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { + return tenantDao.findTenants(tenantId, pageLink); + } - @Override - protected void removeEntity(TenantId tenantId, Tenant entity) { - deleteTenant(TenantId.fromUUID(entity.getUuidId())); - } - }; + @Override + protected void removeEntity(TenantId tenantId, Tenant entity) { + deleteTenant(TenantId.fromUUID(entity.getUuidId())); + } + }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 794c4d52d5..3b5e45f206 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -141,7 +141,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { List>> futures = queries.stream().map(query -> findAllAsync(tenantId, entityId, query)).collect(Collectors.toList()); - return Futures.transform(Futures.allAsList(futures), new Function>, List>() { + return Futures.transform(Futures.allAsList(futures), new Function<>() { @Nullable @Override public List apply(@Nullable List> results) { @@ -254,6 +254,10 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitionsToDelete); deletePartitionAsync(tenantId, cursor, resultFuture); + + for (Long partition : partitionsToDelete) { + cassandraTsPartitionsCache.invalidate(new CassandraPartitionCacheKey(entityId, query.getKey(), partition)); + } } @Override @@ -270,18 +274,20 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD if (query.getAggregation() == Aggregation.NONE) { return findAllAsyncWithLimit(tenantId, entityId, query); } else { + long startPeriod = query.getStartTs(); + long endPeriod = query.getEndTs(); long step = Math.max(query.getInterval(), MIN_AGGREGATION_STEP_MS); - long stepTs = query.getStartTs(); List>> futures = new ArrayList<>(); - while (stepTs < query.getEndTs()) { - long startTs = stepTs; - long endTs = stepTs + step; - ReadTsKvQuery subQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation(), query.getOrder()); + while (startPeriod <= endPeriod) { + long startTs = startPeriod; + long endTs = Math.min(startPeriod + step, endPeriod + 1); + long ts = endTs - startTs; + ReadTsKvQuery subQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, ts, 1, query.getAggregation(), query.getOrder()); futures.add(findAndAggregateAsync(tenantId, entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs))); - stepTs = endTs; + startPeriod = endTs; } ListenableFuture>> future = Futures.allAsList(futures); - return Futures.transform(future, new Function>, List>() { + return Futures.transform(future, new Function<>() { @Nullable @Override public List apply(@Nullable List> input) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraTsPartitionsCache.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraTsPartitionsCache.java index 90943b427a..2ec9e0a596 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraTsPartitionsCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraTsPartitionsCache.java @@ -39,4 +39,8 @@ public class CassandraTsPartitionsCache { public void put(CassandraPartitionCacheKey key) { partitionsCache.put(key, CompletableFuture.completedFuture(true)); } + + public void invalidate(CassandraPartitionCacheKey key) { + partitionsCache.synchronous().invalidate(key); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index db52629d8d..f2bd8e47c1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -35,8 +35,8 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantProfileDao; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.ArrayList; @@ -52,16 +52,16 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A private final ApiUsageStateDao apiUsageStateDao; private final TenantProfileDao tenantProfileDao; - private final TenantDao tenantDao; + private final TenantService tenantService; private final TimeseriesService tsService; private final DataValidator apiUsageStateValidator; public ApiUsageStateServiceImpl(ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao, - TenantDao tenantDao, @Lazy TimeseriesService tsService, + TenantService tenantService, @Lazy TimeseriesService tsService, DataValidator apiUsageStateValidator) { this.apiUsageStateDao = apiUsageStateDao; this.tenantProfileDao = tenantProfileDao; - this.tenantDao = tenantDao; + this.tenantService = tenantService; this.tsService = tsService; this.apiUsageStateValidator = apiUsageStateValidator; } @@ -118,7 +118,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { tenantId = (TenantId) entityId; - Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); + Tenant tenant = tenantService.findTenantById(tenantId); TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index 24b5dc837e..7c8a8ca27b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.springframework.beans.factory.annotation.Value; @@ -50,6 +51,7 @@ import static org.thingsboard.server.dao.service.Validator.validateString; @Service @Slf4j +@RequiredArgsConstructor public class UserServiceImpl extends AbstractEntityService implements UserService { public static final String USER_PASSWORD_HISTORY = "userPasswordHistory"; @@ -73,20 +75,6 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic private final DataValidator userCredentialsValidator; private final ApplicationEventPublisher eventPublisher; - public UserServiceImpl(UserDao userDao, - UserCredentialsDao userCredentialsDao, - UserAuthSettingsDao userAuthSettingsDao, - DataValidator userValidator, - DataValidator userCredentialsValidator, - ApplicationEventPublisher eventPublisher) { - this.userDao = userDao; - this.userCredentialsDao = userCredentialsDao; - this.userAuthSettingsDao = userAuthSettingsDao; - this.userValidator = userValidator; - this.userCredentialsValidator = userCredentialsValidator; - this.eventPublisher = eventPublisher; - } - @Override public User findUserByEmail(TenantId tenantId, String email) { log.trace("Executing findUserByEmail [{}]", email); diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java index 7952f8e22d..4c45239921 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java @@ -30,6 +30,7 @@ import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.stats.DefaultCounter; @@ -38,6 +39,7 @@ import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.nosql.CassandraStatementTask; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import javax.annotation.Nullable; import java.util.HashMap; @@ -71,8 +73,6 @@ public abstract class AbstractBufferedRateExecutor perTenantLimits = new ConcurrentHashMap<>(); private final AtomicInteger printQueriesIdx = new AtomicInteger(0); @@ -80,15 +80,15 @@ public abstract class AbstractBufferedRateExecutor tenantNamesCache = new HashMap<>(); + private final TbTenantProfileCache tenantProfileCache; private final boolean printTenantNames; + private final Map tenantNamesCache = new HashMap<>(); - public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, int callbackThreads, long pollMs, - boolean perTenantLimitsEnabled, String perTenantLimitsConfiguration, int printQueriesFreq, StatsFactory statsFactory, - EntityService entityService, boolean printTenantNames) { + public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, + int callbackThreads, long pollMs, int printQueriesFreq, StatsFactory statsFactory, + EntityService entityService, TbTenantProfileCache tenantProfileCache, boolean printTenantNames) { this.maxWaitTime = maxWaitTime; this.pollMs = pollMs; this.concurrencyLimit = concurrencyLimit; @@ -97,13 +97,12 @@ public abstract class AbstractBufferedRateExecutor settableFuture = create(); F result = wrap(task, settableFuture); + boolean perTenantLimitReached = false; - if (perTenantLimitsEnabled) { + + var tenantProfileConfiguration = + (task.getTenantId() != null && !TenantId.SYS_TENANT_ID.equals(task.getTenantId())) + ? tenantProfileCache.get(task.getTenantId()).getDefaultProfileConfiguration() + : null; + if (tenantProfileConfiguration != null && + StringUtils.isNotEmpty(tenantProfileConfiguration.getCassandraQueryTenantRateLimitsConfiguration())) { if (task.getTenantId() == null) { log.info("Invalid task received: {}", task); } else if (!task.getTenantId().isNullUid()) { - TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(task.getTenantId(), id -> new TbRateLimits(perTenantLimitsConfiguration)); + TbRateLimits rateLimits = perTenantLimits.computeIfAbsent( + task.getTenantId(), id -> new TbRateLimits(tenantProfileConfiguration.getCassandraQueryTenantRateLimitsConfiguration()) + ); if (!rateLimits.tryConsume()) { stats.incrementRateLimitedTenant(task.getTenantId()); stats.getTotalRateLimited().increment(); @@ -128,7 +136,10 @@ public abstract class AbstractBufferedRateExecutor { +public interface WidgetsBundleDao extends Dao, ExportableEntityDao { /** * Save or update widgets bundle object diff --git a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java index df5e4a29f1..737043ccba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/widget/WidgetsBundleServiceImpl.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.entity.AbstractCachedEntityService; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -59,7 +60,12 @@ public class WidgetsBundleServiceImpl implements WidgetsBundleService { public WidgetsBundle saveWidgetsBundle(WidgetsBundle widgetsBundle) { log.trace("Executing saveWidgetsBundle [{}]", widgetsBundle); widgetsBundleValidator.validate(widgetsBundle, WidgetsBundle::getTenantId); - return widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle); + try { + return widgetsBundleDao.save(widgetsBundle.getTenantId(), widgetsBundle); + } catch (Exception e) { + AbstractCachedEntityService.checkConstraintViolation(e, "widgets_bundle_external_id_unq_key", "Widget Bundle with such external id already exists!"); + throw e; + } } @Override diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index b74d935c12..80a5b03b92 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -51,3 +51,25 @@ CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribu CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time); CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id); + +CREATE INDEX IF NOT EXISTS idx_device_external_id ON device(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_device_profile_external_id ON device_profile(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_asset_external_id ON asset(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_entity_view_external_id ON entity_view(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_rule_chain_external_id ON rule_chain(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_dashboard_external_id ON dashboard(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_customer_external_id ON customer(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_widgets_bundle_external_id ON widgets_bundle(tenant_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_rule_node_external_id ON rule_node(rule_chain_id, external_id); + +CREATE INDEX IF NOT EXISTS idx_rule_node_type ON rule_node(type); + +CREATE INDEX IF NOT EXISTS idx_api_usage_state_entity_id ON api_usage_state(entity_id); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index ad9835af33..a9b25a66f7 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -34,6 +34,7 @@ call insert_tb_schema_settings(); CREATE TABLE IF NOT EXISTS admin_settings ( id uuid NOT NULL CONSTRAINT admin_settings_pkey PRIMARY KEY, + tenant_id uuid NOT NULL, created_time bigint NOT NULL, json_value varchar, key varchar(255) @@ -82,7 +83,9 @@ CREATE TABLE IF NOT EXISTS asset ( search_text varchar(255), tenant_id uuid, type varchar(255), - CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name) + external_id uuid, + CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name), + CONSTRAINT asset_external_id_unq_key UNIQUE (tenant_id, external_id) ); CREATE TABLE IF NOT EXISTS audit_log ( @@ -141,7 +144,9 @@ CREATE TABLE IF NOT EXISTS customer ( state varchar(255), tenant_id uuid, title varchar(255), - zip varchar(255) + zip varchar(255), + external_id uuid, + CONSTRAINT customer_external_id_unq_key UNIQUE (tenant_id, external_id) ); CREATE TABLE IF NOT EXISTS dashboard ( @@ -154,7 +159,9 @@ CREATE TABLE IF NOT EXISTS dashboard ( title varchar(255), mobile_hide boolean DEFAULT false, mobile_order int, - image varchar(1000000) + image varchar(1000000), + external_id uuid, + CONSTRAINT dashboard_external_id_unq_key UNIQUE (tenant_id, external_id) ); CREATE TABLE IF NOT EXISTS rule_chain ( @@ -168,7 +175,9 @@ CREATE TABLE IF NOT EXISTS rule_chain ( root boolean, debug_mode boolean, search_text varchar(255), - tenant_id uuid + tenant_id uuid, + external_id uuid, + CONSTRAINT rule_chain_external_id_unq_key UNIQUE (tenant_id, external_id) ); CREATE TABLE IF NOT EXISTS rule_node ( @@ -180,7 +189,8 @@ CREATE TABLE IF NOT EXISTS rule_node ( type varchar(255), name varchar(255), debug_mode boolean, - search_text varchar(255) + search_text varchar(255), + external_id uuid ); CREATE TABLE IF NOT EXISTS rule_node_state ( @@ -215,7 +225,7 @@ CREATE TABLE IF NOT EXISTS ota_package ( CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) ); -CREATE TABLE IF NOT EXISTS queue( +CREATE TABLE IF NOT EXISTS queue ( id uuid NOT NULL CONSTRAINT queue_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid, @@ -247,13 +257,14 @@ CREATE TABLE IF NOT EXISTS device_profile ( software_id uuid, default_rule_chain_id uuid, default_dashboard_id uuid, - default_queue_id uuid, + default_queue_name varchar(255), provision_device_key varchar, + external_id uuid, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), + CONSTRAINT device_profile_external_id_unq_key UNIQUE (tenant_id, external_id), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id), - CONSTRAINT fk_default_queue_device_profile FOREIGN KEY (default_queue_id) REFERENCES queue(id), CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES ota_package(id), CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id) ); @@ -293,7 +304,9 @@ CREATE TABLE IF NOT EXISTS device ( tenant_id uuid, firmware_id uuid, software_id uuid, + external_id uuid, CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), + CONSTRAINT device_external_id_unq_key UNIQUE (tenant_id, external_id), CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id), CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES ota_package(id), CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES ota_package(id) @@ -416,7 +429,9 @@ CREATE TABLE IF NOT EXISTS widgets_bundle ( tenant_id uuid, title varchar(255), image varchar(1000000), - description varchar(255) + description varchar(255), + external_id uuid, + CONSTRAINT widgets_bundle_external_id_unq_key UNIQUE (tenant_id, external_id) ); CREATE TABLE IF NOT EXISTS entity_view ( @@ -432,7 +447,9 @@ CREATE TABLE IF NOT EXISTS entity_view ( start_ts bigint, end_ts bigint, search_text varchar(255), - additional_info varchar + additional_info varchar, + external_id uuid, + CONSTRAINT entity_view_external_id_unq_key UNIQUE (tenant_id, external_id) ); CREATE TABLE IF NOT EXISTS ts_kv_latest diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index ef163b2a06..964e6229f1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -65,6 +65,7 @@ import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -170,6 +171,9 @@ public abstract class AbstractServiceTest { @Autowired protected OtaPackageService otaPackageService; + @Autowired + protected RpcService rpcService; + @Autowired protected QueueService queueService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java index 0bbbf82452..093bd386a7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEdgeServiceTest.java @@ -16,11 +16,13 @@ package org.thingsboard.server.dao.service; import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.fasterxml.jackson.databind.node.ObjectNode; import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Tenant; @@ -29,9 +31,14 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -602,4 +609,55 @@ public abstract class BaseEdgeServiceTest extends AbstractServiceTest { edgeService.deleteEdge(tenantId, savedEdge.getId()); } -} \ No newline at end of file + @Test + public void testFindMissingToRelatedRuleChains() { + Edge edge = constructEdge("My edge", "default"); + Edge savedEdge = edgeService.saveEdge(edge); + + RuleChain ruleChain = new RuleChain(); + ruleChain.setTenantId(tenantId); + ruleChain.setName("Rule Chain #1"); + ruleChain.setType(RuleChainType.EDGE); + RuleChain ruleChain1 = ruleChainService.saveRuleChain(ruleChain); + + ruleChain = new RuleChain(); + ruleChain.setTenantId(tenantId); + ruleChain.setName("Rule Chain #2"); + ruleChain.setType(RuleChainType.EDGE); + RuleChain ruleChain2 = ruleChainService.saveRuleChain(ruleChain); + + ruleChain = new RuleChain(); + ruleChain.setTenantId(tenantId); + ruleChain.setName("Rule Chain #3"); + ruleChain.setType(RuleChainType.EDGE); + RuleChain ruleChain3 = ruleChainService.saveRuleChain(ruleChain); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("Input rule node 1"); + ruleNode1.setType("org.thingsboard.rule.engine.flow.TbRuleChainInputNode"); + ObjectNode configuration = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + configuration.put("ruleChainId", ruleChain1.getUuidId().toString()); + ruleNode1.setConfiguration(configuration); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("Input rule node 2"); + ruleNode2.setType("org.thingsboard.rule.engine.flow.TbRuleChainInputNode"); + configuration = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + configuration.put("ruleChainId", ruleChain2.getUuidId().toString()); + ruleNode2.setConfiguration(configuration); + + RuleChainMetaData ruleChainMetaData3 = new RuleChainMetaData(); + ruleChainMetaData3.setNodes(Arrays.asList(ruleNode1, ruleNode2)); + ruleChainMetaData3.setFirstNodeIndex(0); + ruleChainMetaData3.setRuleChainId(ruleChain3.getId()); + ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData3); + + ruleChainService.assignRuleChainToEdge(tenantId, ruleChain3.getId(), savedEdge.getId()); + + String missingToRelatedRuleChains = edgeService.findMissingToRelatedRuleChains(tenantId, + savedEdge.getId(), + "org.thingsboard.rule.engine.flow.TbRuleChainInputNode"); + Assert.assertEquals("{\"Rule Chain #3\":[\"Rule Chain #1\",\"Rule Chain #2\"]}", missingToRelatedRuleChains); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java index 6f088c79f6..ade4ce8c28 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java @@ -97,26 +97,26 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId)); - createFirmware(tenantId, "1"); + createAndSaveFirmware(tenantId, "1"); Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId)); thrown.expect(DataValidationException.class); thrown.expectMessage(String.format("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE)); - createFirmware(tenantId, "2"); + createAndSaveFirmware(tenantId, "2"); } @Test public void sumDataSizeByTenantId() { Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId)); - createFirmware(tenantId, "0.1"); + createAndSaveFirmware(tenantId, "0.1"); Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId)); int maxSumDataSize = 8; List packages = new ArrayList<>(maxSumDataSize); for (int i = 2; i <= maxSumDataSize; i++) { - packages.add(createFirmware(tenantId, "0." + i)); + packages.add(createAndSaveFirmware(tenantId, "0." + i)); Assert.assertEquals(i, otaPackageService.sumDataSizeByTenantId(tenantId)); } @@ -419,15 +419,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @Test public void testSaveFirmwareWithExistingTitleAndVersion() { - createFirmware(tenantId, VERSION); + createAndSaveFirmware(tenantId, VERSION); thrown.expect(DataValidationException.class); thrown.expectMessage("OtaPackage with such title and version already exists!"); - createFirmware(tenantId, VERSION); + createAndSaveFirmware(tenantId, VERSION); } @Test public void testDeleteFirmwareWithReferenceByDevice() { - OtaPackage savedFirmware = createFirmware(tenantId, VERSION); + OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION); Device device = new Device(); device.setTenantId(tenantId); @@ -448,7 +448,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @Test public void testUpdateDeviceProfileId() { - OtaPackage savedFirmware = createFirmware(tenantId, VERSION); + OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION); try { thrown.expect(DataValidationException.class); @@ -493,7 +493,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @Test public void testFindFirmwareById() { - OtaPackage savedFirmware = createFirmware(tenantId, VERSION); + OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION); OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); Assert.assertNotNull(foundFirmware); @@ -519,7 +519,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { @Test public void testDeleteFirmware() { - OtaPackage savedFirmware = createFirmware(tenantId, VERSION); + OtaPackage savedFirmware = createAndSaveFirmware(tenantId, VERSION); OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId()); Assert.assertNotNull(foundFirmware); @@ -532,7 +532,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { public void testFindTenantFirmwaresByTenantId() { List firmwares = new ArrayList<>(); for (int i = 0; i < 165; i++) { - OtaPackageInfo info = new OtaPackageInfo(createFirmware(tenantId, VERSION + i)); + OtaPackageInfo info = new OtaPackageInfo(createAndSaveFirmware(tenantId, VERSION + i)); info.setHasData(true); firmwares.add(info); } @@ -579,7 +579,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { public void testFindTenantFirmwaresByTenantIdAndHasData() { List firmwares = new ArrayList<>(); for (int i = 0; i < 165; i++) { - firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createFirmware(tenantId, VERSION + i)))); + firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createAndSaveFirmware(tenantId, VERSION + i)))); } OtaPackageInfo firmwareWithUrl = new OtaPackageInfo(); @@ -695,7 +695,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { otaPackageService.saveOtaPackageInfo(firmwareInfo, true); } - private OtaPackage createFirmware(TenantId tenantId, String version) { + private OtaPackage createAndSaveFirmware(TenantId tenantId, String version) { + return otaPackageService.saveOtaPackage(createFirmware(tenantId, version, deviceProfileId)); + } + + public static OtaPackage createFirmware( + TenantId tenantId, + String version, + DeviceProfileId deviceProfileId + ) { OtaPackage firmware = new OtaPackage(); firmware.setTenantId(tenantId); firmware.setDeviceProfileId(deviceProfileId); @@ -708,6 +716,6 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest { firmware.setChecksum(CHECKSUM); firmware.setData(DATA); firmware.setDataSize(DATA_SIZE); - return otaPackageService.saveOtaPackage(firmware); + return firmware; } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseQueueServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseQueueServiceTest.java index 577193ff94..c2365ab1e9 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseQueueServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseQueueServiceTest.java @@ -51,7 +51,6 @@ public abstract class BaseQueueServiceTest extends AbstractServiceTest { tenantProfile.setDefault(false); tenantProfile.setName("Isolated TB Rule Engine"); tenantProfile.setDescription("Isolated TB Rule Engine tenant profile"); - tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbRuleEngine(true); TenantProfileQueueConfiguration mainQueueConfiguration = new TenantProfileQueueConfiguration(); @@ -152,6 +151,20 @@ public abstract class BaseQueueServiceTest extends AbstractServiceTest { queueService.saveQueue(queue); } + @Test(expected = DataValidationException.class) + public void testSaveQueueWithInvalidName() { + Queue queue = new Queue(); + queue.setTenantId(tenantId); + queue.setName("Test 1"); + queue.setTopic("tb_rule_engine.test"); + queue.setPollInterval(25); + queue.setPartitions(1); + queue.setPackProcessingTimeout(2000); + queue.setSubmitStrategy(createTestSubmitStrategy()); + queue.setProcessingStrategy(createTestProcessingStrategy()); + queueService.saveQueue(queue); + } + @Test(expected = DataValidationException.class) public void testSaveQueueWithEmptyTopic() { Queue queue = new Queue(); @@ -165,6 +178,20 @@ public abstract class BaseQueueServiceTest extends AbstractServiceTest { queueService.saveQueue(queue); } + @Test(expected = DataValidationException.class) + public void testSaveQueueWithInvalidTopic() { + Queue queue = new Queue(); + queue.setTenantId(tenantId); + queue.setName("Test"); + queue.setTopic("tb rule engine test"); + queue.setPollInterval(25); + queue.setPartitions(1); + queue.setPackProcessingTimeout(2000); + queue.setSubmitStrategy(createTestSubmitStrategy()); + queue.setProcessingStrategy(createTestProcessingStrategy()); + queueService.saveQueue(queue); + } + @Test(expected = DataValidationException.class) public void testSaveQueueWithEmptyPollInterval() { Queue queue = new Queue(); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index 1f167976e5..75ac95d470 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java @@ -57,13 +57,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertTrue(saveRelation(relation)); - Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); + Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); - Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get()); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON)); - Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); - Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON).get()); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, parentId, "NOT_EXISTING_TYPE", RelationTypeGroup.COMMON)); } @Test @@ -80,9 +80,9 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, relationA).get()); - Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); - Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); + Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); } @@ -116,11 +116,11 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { saveRelation(relationA); saveRelation(relationB); - Assert.assertNull(relationService.deleteEntityRelationsAsync(SYSTEM_TENANT_ID, childId).get()); + relationService.deleteEntityRelations(SYSTEM_TENANT_ID, childId); - Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); - Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON).get()); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); } @Test diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java index 575fab55df..f245af61c1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java @@ -85,7 +85,6 @@ public abstract class BaseTenantProfileServiceTest extends AbstractServiceTest { Assert.assertEquals(tenantProfile.getDescription(), savedTenantProfile.getDescription()); Assert.assertEquals(tenantProfile.getProfileData(), savedTenantProfile.getProfileData()); Assert.assertEquals(tenantProfile.isDefault(), savedTenantProfile.isDefault()); - Assert.assertEquals(tenantProfile.isIsolatedTbCore(), savedTenantProfile.isIsolatedTbCore()); Assert.assertEquals(tenantProfile.isIsolatedTbRuleEngine(), savedTenantProfile.isIsolatedTbRuleEngine()); savedTenantProfile.setName("New tenant profile"); @@ -177,14 +176,6 @@ public abstract class BaseTenantProfileServiceTest extends AbstractServiceTest { tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, savedTenantProfile); } - @Test(expected = DataValidationException.class) - public void testSaveSameTenantProfileWithDifferentIsolatedTbCore() { - TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"); - TenantProfile savedTenantProfile = tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile); - savedTenantProfile.setIsolatedTbCore(true); - tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, savedTenantProfile); - } - @Test(expected = DataValidationException.class) public void testDeleteTenantProfileWithExistingTenant() { TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"); @@ -298,7 +289,6 @@ public abstract class BaseTenantProfileServiceTest extends AbstractServiceTest { profileData.setConfiguration(new DefaultTenantProfileConfiguration()); tenantProfile.setProfileData(profileData); tenantProfile.setDefault(false); - tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbRuleEngine(false); return tenantProfile; } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java index 6017883514..4a48c56157 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantServiceTest.java @@ -18,25 +18,69 @@ package org.thingsboard.server.dao.service; import org.apache.commons.lang3.RandomStringUtils; import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; +import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; public abstract class BaseTenantServiceTest extends AbstractServiceTest { - + private IdComparator idComparator = new IdComparator<>(); + @SpyBean + protected TenantDao tenantDao; + + @Autowired + protected TbTransactionalCache cache; + + @Autowired + protected TbTransactionalCache existsTenantCache; + @Test public void testSaveTenant() { Tenant tenant = new Tenant(); @@ -46,15 +90,15 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { Assert.assertNotNull(savedTenant.getId()); Assert.assertTrue(savedTenant.getCreatedTime() > 0); Assert.assertEquals(tenant.getTitle(), savedTenant.getTitle()); - + savedTenant.setTitle("My new tenant"); tenantService.saveTenant(savedTenant); Tenant foundTenant = tenantService.findTenantById(savedTenant.getId()); Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle()); - + tenantService.deleteTenant(savedTenant.getId()); } - + @Test public void testFindTenantById() { Tenant tenant = new Tenant(); @@ -76,13 +120,13 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { Assert.assertEquals(new TenantInfo(savedTenant, "Default"), foundTenant); tenantService.deleteTenant(savedTenant.getId()); } - + @Test(expected = DataValidationException.class) public void testSaveTenantWithEmptyTitle() { Tenant tenant = new Tenant(); tenantService.saveTenant(tenant); } - + @Test(expected = DataValidationException.class) public void testSaveTenantWithInvalidEmail() { Tenant tenant = new Tenant(); @@ -90,7 +134,7 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { tenant.setEmail("invalid@mail"); tenantService.saveTenant(tenant); } - + @Test public void testDeleteTenant() { Tenant tenant = new Tenant(); @@ -100,23 +144,22 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { Tenant foundTenant = tenantService.findTenantById(savedTenant.getId()); Assert.assertNull(foundTenant); } - + @Test public void testFindTenants() { - List tenants = new ArrayList<>(); PageLink pageLink = new PageLink(17); PageData pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); tenants.addAll(pageData.getData()); - - for (int i=0;i<156;i++) { + + for (int i = 0; i < 156; i++) { Tenant tenant = new Tenant(); - tenant.setTitle("Tenant"+i); + tenant.setTitle("Tenant" + i); tenants.add(tenantService.saveTenant(tenant)); } - + List loadedTenants = new ArrayList<>(); pageLink = new PageLink(17); do { @@ -126,46 +169,46 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(tenants, idComparator); Collections.sort(loadedTenants, idComparator); - + Assert.assertEquals(tenants, loadedTenants); - + for (Tenant tenant : loadedTenants) { tenantService.deleteTenant(tenant.getId()); } - + pageLink = new PageLink(17); pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); - + } - + @Test public void testFindTenantsByTitle() { String title1 = "Tenant title 1"; List tenantsTitle1 = new ArrayList<>(); - for (int i=0;i<134;i++) { + for (int i = 0; i < 134; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); - String title = title1+suffix; + String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String title = title1 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); tenantsTitle1.add(tenantService.saveTenant(tenant)); } String title2 = "Tenant title 2"; List tenantsTitle2 = new ArrayList<>(); - for (int i=0;i<127;i++) { + for (int i = 0; i < 127; i++) { Tenant tenant = new Tenant(); - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); - String title = title2+suffix; + String suffix = RandomStringUtils.randomAlphanumeric((int) (Math.random() * 15)); + String title = title2 + suffix; title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); tenant.setTitle(title); tenantsTitle2.add(tenantService.saveTenant(tenant)); } - + List loadedTenantsTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; @@ -176,12 +219,12 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(tenantsTitle1, idComparator); Collections.sort(loadedTenantsTitle1, idComparator); - + Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1); - + List loadedTenantsTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { @@ -194,22 +237,22 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { Collections.sort(tenantsTitle2, idComparator); Collections.sort(loadedTenantsTitle2, idComparator); - + Assert.assertEquals(tenantsTitle2, loadedTenantsTitle2); for (Tenant tenant : loadedTenantsTitle1) { tenantService.deleteTenant(tenant.getId()); } - + pageLink = new PageLink(4, 0, title1); pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - + for (Tenant tenant : loadedTenantsTitle2) { tenantService.deleteTenant(tenant.getId()); } - + pageLink = new PageLink(4, 0, title2); pageData = tenantService.findTenants(pageLink); Assert.assertFalse(pageData.hasNext()); @@ -226,9 +269,9 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { Assert.assertTrue(pageData.getData().isEmpty()); tenants.addAll(pageData.getData()); - for (int i=0;i<156;i++) { + for (int i = 0; i < 156; i++) { Tenant tenant = new Tenant(); - tenant.setTitle("Tenant"+i); + tenant.setTitle("Tenant" + i); tenants.add(new TenantInfo(tenantService.saveTenant(tenant), "Default")); } @@ -266,7 +309,6 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { profileData.setConfiguration(new DefaultTenantProfileConfiguration()); tenantProfile.setProfileData(profileData); tenantProfile.setDefault(false); - tenantProfile.setIsolatedTbCore(true); tenantProfile.setIsolatedTbRuleEngine(true); TenantProfile isolatedTenantProfile = tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile); @@ -275,4 +317,382 @@ public abstract class BaseTenantServiceTest extends AbstractServiceTest { tenant.setTenantProfileId(isolatedTenantProfile.getId()); tenantService.saveTenant(tenant); } + + @Test + public void testGettingTenantAddingItToCache() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + + Mockito.reset(tenantDao); + + verify(tenantDao, Mockito.times(0)).findById(any(), any()); + tenantService.findTenantById(savedTenant.getId()); + verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId())); + + var cachedTenant = cache.get(savedTenant.getId()); + Assert.assertNotNull("Getting an existing Tenant doesn't add it to the cache!", cachedTenant); + Assert.assertEquals(savedTenant, cachedTenant.get()); + + for (int i = 0; i < 100; i++) { + tenantService.findTenantById(savedTenant.getId()); + } + verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId())); + + tenantService.deleteTenant(savedTenant.getId()); + } + + @Test + public void testExistsTenantAddingResultToCache() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + + Mockito.reset(tenantDao); + //fromIdExists invoked from device profile validator + existsTenantCache.evict(savedTenant.getTenantId()); + + verify(tenantDao, Mockito.times(0)).existsById(any(), any()); + tenantService.tenantExists(savedTenant.getId()); + verify(tenantDao, Mockito.times(1)).existsById(eq(savedTenant.getId()), eq(savedTenant.getId().getId())); + + var isExists = existsTenantCache.get(savedTenant.getId()); + Assert.assertNotNull("Getting an existing Tenant doesn't add it to the cache!", isExists); + + for (int i = 0; i < 100; i++) { + tenantService.tenantExists(savedTenant.getId()); + } + verify(tenantDao, Mockito.times(1)).existsById(eq(savedTenant.getId()), eq(savedTenant.getId().getId())); + + tenantService.deleteTenant(savedTenant.getId()); + } + + @Test + public void testUpdatingExistingTenantEvictCache() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + + tenantService.findTenantById(savedTenant.getId()); + + var cachedTenant = cache.get(savedTenant.getId()); + Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedTenant); + Assert.assertEquals(savedTenant, cachedTenant.get()); + + savedTenant.setTitle("My new tenant"); + savedTenant = tenantService.saveTenant(savedTenant); + + Mockito.reset(tenantDao); + + cachedTenant = cache.get(savedTenant.getId()); + Assert.assertNull("Updating a Tenant doesn't evict the cache!", cachedTenant); + + verify(tenantDao, Mockito.times(0)).findById(any(), any()); + tenantService.findTenantById(savedTenant.getId()); + verify(tenantDao, Mockito.times(1)).findById(eq(savedTenant.getId()), eq(savedTenant.getId().getId())); + + tenantService.deleteTenant(savedTenant.getId()); + } + + @Test + public void testRemovingTenantEvictCache() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + + tenantService.findTenantById(savedTenant.getId()); + tenantService.tenantExists(savedTenant.getId()); + + var cachedTenant = + cache.get(savedTenant.getId()); + var cachedExists = + existsTenantCache.get(savedTenant.getId()); + Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedTenant); + Assert.assertNotNull("Saving a Tenant doesn't add it to the cache!", cachedExists); + + tenantService.deleteTenant(savedTenant.getId()); + cachedTenant = + cache.get(savedTenant.getId()); + cachedExists = + existsTenantCache.get(savedTenant.getId()); + + + Assert.assertNull("Removing a Tenant doesn't evict the cache!", cachedTenant); + Assert.assertNull("Removing a Tenant doesn't evict the cache!", cachedExists); + } + + @Test + public void testDeleteTenantDeletingAllRelatedEntities() throws Exception { + TenantProfile profile = createAndSaveTenantProfile(); + Tenant tenant = createAndSaveTenant(profile); + User user = createAndSaveUserFor(tenant); + Customer customer = createAndSaveCustomerFor(tenant); + WidgetsBundle widgetsBundle = createAndSaveWidgetBundleFor(tenant); + DeviceProfile deviceProfile = createAndSaveDeviceProfileWithProfileDataFor(tenant); + Device device = createAndSaveDeviceFor(tenant, customer, deviceProfile); + EntityView entityView = createAndSaveEntityViewFor(tenant, customer, device); + Asset asset = createAndSaveAssetFor(tenant, customer); + Dashboard dashboard = createAndSaveDashboardFor(tenant, customer); + RuleChain ruleChain = createAndSaveRuleChainFor(tenant); + Edge edge = createAndSaveEdgeFor(tenant); + OtaPackage otaPackage = createAndSaveOtaPackageFor(tenant, deviceProfile); + TbResource resource = createAndSaveResourceFor(tenant); + Rpc rpc = createAndSaveRpcFor(tenant, device); + + tenantService.deleteTenant(tenant.getId()); + + Assert.assertNull(tenantService.findTenantById(tenant.getId())); + assertCustomerIsDeleted(tenant, customer); + assertWidgetsBundleIsDeleted(tenant, widgetsBundle); + assertEntityViewIsDeleted(tenant, device, entityView); + assertAssetIsDeleted(tenant, asset); + assertDeviceIsDeleted(tenant, device); + assertDeviceProfileIsDeleted(tenant, deviceProfile); + assertDashboardIsDeleted(tenant, dashboard); + assertEdgeIsDeleted(tenant, edge); + assertTenantAdminIsDeleted(tenant); + assertUserIsDeleted(tenant, user); + Assert.assertNull(ruleChainService.findRuleChainById(tenant.getId(), ruleChain.getId())); + Assert.assertNull(apiUsageStateService.findTenantApiUsageState(tenant.getId())); + assertResourceIsDeleted(tenant, resource); + assertOtaPackageIsDeleted(tenant, otaPackage); + Assert.assertNull(rpcService.findById(tenant.getId(), rpc.getId())); + + tenantProfileService.deleteTenantProfile(TenantId.SYS_TENANT_ID, profile.getId()); + } + + private void assertOtaPackageIsDeleted(Tenant tenant, OtaPackage otaPackage) { + assertThat(otaPackageService.findOtaPackageById(tenant.getId(), otaPackage.getId())) + .as("otaPackage").isNull(); + PageLink pageLinkOta = new PageLink(1); + PageData pageDataOta = otaPackageService.findTenantOtaPackagesByTenantId(tenant.getId(), pageLinkOta); + Assert.assertEquals(0, pageDataOta.getTotalElements()); + } + + private void assertResourceIsDeleted(Tenant tenant, TbResource resource) { + assertThat(resourceService.findResourceById(tenant.getId(), resource.getId())) + .as("resource").isNull(); + PageLink pageLinkResources = new PageLink(1); + PageData tenantResources = + resourceService.findAllTenantResourcesByTenantId(tenant.getId(), pageLinkResources); + Assert.assertEquals(0, tenantResources.getTotalElements()); + } + + private void assertUserIsDeleted(Tenant tenant, User user) { + assertThat(userService.findUserById(tenant.getId(), user.getId())) + .as("user").isNull(); + PageLink pageLinkUsers = new PageLink(1); + PageData users = + userService.findUsersByTenantId(tenant.getId(), pageLinkUsers); + Assert.assertEquals(0, users.getTotalElements()); + } + + private void assertTenantAdminIsDeleted(Tenant savedTenant) { + PageLink pageLinkTenantAdmins = new PageLink(1); + PageData tenantAdmins = + userService.findTenantAdmins(savedTenant.getId(), pageLinkTenantAdmins); + Assert.assertEquals(0, tenantAdmins.getTotalElements()); + } + + private void assertEdgeIsDeleted(Tenant tenant, Edge edge) { + assertThat(edgeService.findEdgeById(tenant.getId(), edge.getId())) + .as("edge").isNull(); + PageLink pageLinkEdges = new PageLink(1); + PageData edges = edgeService.findEdgesByTenantId(tenant.getId(), pageLinkEdges); + Assert.assertEquals(0, edges.getTotalElements()); + } + + private void assertDashboardIsDeleted(Tenant tenant, Dashboard dashboard) { + assertThat(dashboardService.findDashboardById(tenant.getId(), dashboard.getId())) + .as("dashboard").isNull(); + PageLink pageLinkDashboards = new PageLink(1); + PageData dashboards = + dashboardService.findDashboardsByTenantId(tenant.getId(), pageLinkDashboards); + Assert.assertEquals(0, dashboards.getTotalElements()); + } + + private void assertDeviceProfileIsDeleted(Tenant tenant, DeviceProfile deviceProfile) { + assertThat(deviceProfileService.findDeviceProfileById(tenant.getId(), deviceProfile.getId())) + .as("deviceProfile").isNull(); + PageLink pageLinkDeviceProfiles = new PageLink(1); + PageData profiles = + deviceProfileService.findDeviceProfiles(tenant.getId(), pageLinkDeviceProfiles); + Assert.assertEquals(0, profiles.getTotalElements()); + } + + private void assertDeviceIsDeleted(Tenant tenant, Device device) { + assertThat(deviceService.findDeviceById(tenant.getId(), device.getId())) + .as("device").isNull(); + PageLink pageLinkDevices = new PageLink(1); + PageData devices = + deviceService.findDevicesByTenantId(tenant.getId(), pageLinkDevices); + Assert.assertEquals(0, devices.getTotalElements()); + } + + private void assertAssetIsDeleted(Tenant tenant, Asset asset) { + assertThat(assetService.findAssetById(tenant.getId(), asset.getId())) + .as("asset").isNull(); + PageLink pageLinkAssets = new PageLink(1); + PageData assets = + assetService.findAssetsByTenantId(tenant.getId(), pageLinkAssets); + Assert.assertEquals(0, assets.getTotalElements()); + } + + private void assertEntityViewIsDeleted(Tenant tenant, Device device, EntityView entityView) { + assertThat(entityViewService.findEntityViewById(tenant.getId(), entityView.getId())) + .as("entityView").isNull(); + List entityViews = + entityViewService.findEntityViewsByTenantIdAndEntityId(tenant.getId(), device.getId()); + Assert.assertTrue(entityViews.isEmpty()); + } + + private void assertWidgetsBundleIsDeleted(Tenant tenant, WidgetsBundle widgetsBundle) { + assertThat(widgetsBundleService.findWidgetsBundleById(tenant.getId(), widgetsBundle.getId())) + .as("widgetBundle").isNull(); + List widgetsBundlesByTenantId = + widgetsBundleService.findAllTenantWidgetsBundlesByTenantId(tenant.getId()); + Assert.assertTrue(widgetsBundlesByTenantId.isEmpty()); + } + + private void assertCustomerIsDeleted(Tenant tenant, Customer customer) { + assertThat(customerService.findCustomerById(tenant.getId(), customer.getId())) + .as("customer").isNull(); + PageLink pageLinkCustomer = new PageLink(1); + PageData pageDataCustomer = customerService + .findCustomersByTenantId(tenant.getId(), pageLinkCustomer); + Assert.assertEquals(0, pageDataCustomer.getTotalElements()); + } + + private Rpc createAndSaveRpcFor(Tenant tenant, Device device) { + Rpc rpc = new Rpc(); + rpc.setTenantId(tenant.getId()); + rpc.setDeviceId(device.getId()); + rpc.setStatus(RpcStatus.QUEUED); + rpc.setRequest(JacksonUtil.toJsonNode("{}")); + return rpcService.save(rpc); + } + + private TbResource createAndSaveResourceFor(Tenant tenant) { + TbResource resource = new TbResource(); + resource.setTenantId(tenant.getId()); + resource.setTitle("Test resource"); + resource.setResourceType(ResourceType.LWM2M_MODEL); + resource.setFileName("filename.txt"); + resource.setResourceKey("Test resource key"); + resource.setData("Some super test data"); + return resourceService.saveResource(resource); + } + + private OtaPackage createAndSaveOtaPackageFor(Tenant tenant, DeviceProfile deviceProfile) { + return otaPackageService.saveOtaPackage( + BaseOtaPackageServiceTest.createFirmware( + tenant.getId(), "2", deviceProfile.getId()) + ); + } + + private Edge createAndSaveEdgeFor(Tenant tenant) { + Edge edge = constructEdge(tenant.getId(), "Test edge", "Simple"); + return edgeService.saveEdge(edge); + } + + private RuleChain createAndSaveRuleChainFor(Tenant tenant) { + RuleChain ruleChain = new RuleChain(); + ruleChain.setTenantId(tenant.getId()); + ruleChain.setName("Test rule chain"); + ruleChain.setType(RuleChainType.CORE); + return ruleChainService.saveRuleChain(ruleChain); + } + + private Dashboard createAndSaveDashboardFor(Tenant tenant, Customer customer) { + Dashboard dashboard = new Dashboard(); + dashboard.setTenantId(tenant.getId()); + dashboard.setTitle("Test dashboard"); + dashboard.setAssignedCustomers(Set.of(customer.toShortCustomerInfo())); + return dashboardService.saveDashboard(dashboard); + } + + private Asset createAndSaveAssetFor(Tenant tenant, Customer customer) { + Asset asset = new Asset(); + asset.setTenantId(tenant.getId()); + asset.setCustomerId(customer.getId()); + asset.setType("Test asset type"); + asset.setName("Test asset type"); + asset.setLabel("Test asset type"); + return assetService.saveAsset(asset); + } + + private EntityView createAndSaveEntityViewFor(Tenant tenant, Customer customer, Device device) { + EntityView entityView = new EntityView(); + entityView.setEntityId(device.getId()); + entityView.setTenantId(tenant.getId()); + entityView.setCustomerId(customer.getId()); + entityView.setType("Test type"); + entityView.setName("Test entity view"); + entityView.setStartTimeMs(0); + entityView.setEndTimeMs(840000); + return entityViewService.saveEntityView(entityView); + } + + private Device createAndSaveDeviceFor(Tenant tenant, Customer customer, DeviceProfile deviceProfile) { + Device device = new Device(); + device.setCustomerId(customer.getId()); + device.setTenantId(tenant.getId()); + device.setType("Test type"); + device.setName("TestType"); + device.setLabel("Test type"); + device.setDeviceProfileId(deviceProfile.getId()); + return deviceService.saveDevice(device); + } + + private DeviceProfile createAndSaveDeviceProfileWithProfileDataFor(Tenant tenant) { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setTenantId(tenant.getId()); + deviceProfile.setTransportType(DeviceTransportType.MQTT); + deviceProfile.setName("Test device profile"); + deviceProfile.setType(DeviceProfileType.DEFAULT); + DeviceProfileData profileData = new DeviceProfileData(); + profileData.setTransportConfiguration(new MqttDeviceProfileTransportConfiguration()); + deviceProfile.setProfileData(profileData); + return deviceProfileService.saveDeviceProfile(deviceProfile); + } + + private WidgetsBundle createAndSaveWidgetBundleFor(Tenant tenant) { + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTenantId(tenant.getId()); + widgetsBundle.setTitle("Test widgets bundle"); + widgetsBundle.setAlias("TestWidgetsBundle"); + widgetsBundle.setDescription("Just a simple widgets bundle"); + return widgetsBundleService.saveWidgetsBundle(widgetsBundle); + } + + private Customer createAndSaveCustomerFor(Tenant tenant) { + Customer customer = new Customer(); + customer.setTitle("Test customer"); + customer.setTenantId(tenant.getId()); + customer.setEmail("testCustomer@test.com"); + return customerService.saveCustomer(customer); + } + + private User createAndSaveUserFor(Tenant tenant) { + User user = new User(); + user.setAuthority(Authority.TENANT_ADMIN); + user.setEmail("tenantAdmin@test.com"); + user.setFirstName("tenantAdmin"); + user.setLastName("tenantAdmin"); + user.setTenantId(tenant.getId()); + return userService.saveUser(user); + } + + private Tenant createAndSaveTenant(TenantProfile tenantProfile) { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + tenant.setTenantProfileId(tenantProfile.getId()); + return tenantService.saveTenant(tenant); + } + + private TenantProfile createAndSaveTenantProfile() { + TenantProfile tenantProfile = new TenantProfile(); + tenantProfile.setName("Test tenant profile"); + return tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile); + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index 4142034729..5339863044 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -184,6 +184,140 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { Assert.assertEquals(toTsEntry(TS - 1, stringKvEntry), entries.get(2)); } + @Test + public void testFindByQuery_whenPeriodEqualsOneMilisecondPeriod() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS - 1L); + saveEntries(deviceId, TS); + saveEntries(deviceId, TS + 1L); + + List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS, 1, 1, Aggregation.COUNT, DESC_ORDER)); + + List entries = tsService.findAll(tenantId, deviceId, queries).get(); + Assert.assertEquals(1, entries.size()); + Assert.assertEquals(toTsEntry(TS, new LongDataEntry(LONG_KEY, 1L)), entries.get(0)); + + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); + + entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); + Assert.assertEquals(1, entries.size()); + Assert.assertEquals(toTsEntry(TS, new LongDataEntry(LONG_KEY, 1L)), entries.get(0)); + } + + @Test + public void testFindByQuery_whenPeriodEqualsInterval() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS - 1L); + for (long i = TS; i <= TS + 100L; i += 10L) { + saveEntries(deviceId, i); + } + saveEntries(deviceId, TS + 100L + 1L); + + List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS + 100, 101, 1, Aggregation.COUNT, DESC_ORDER)); + + List entries = tsService.findAll(tenantId, deviceId, queries).get(); + Assert.assertEquals(1, entries.size()); + Assert.assertEquals(toTsEntry(TS + 50, new LongDataEntry(LONG_KEY, 11L)), entries.get(0)); + + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); + + entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); + Assert.assertEquals(1, entries.size()); + Assert.assertEquals(toTsEntry(TS + 50, new LongDataEntry(LONG_KEY, 11L)), entries.get(0)); + } + + @Test + public void testFindByQuery_whenPeriodHaveTwoIntervalWithEqualsLength() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS - 1L); + for (long i = TS; i <= TS + 100000L; i += 10000L) { + saveEntries(deviceId, i); + } + saveEntries(deviceId, TS + 100000L + 1L); + + List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS + 99999, 50000, 1, Aggregation.COUNT, DESC_ORDER)); + + List entries = tsService.findAll(tenantId, deviceId, queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); + + entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + } + + @Test + public void testFindByQuery_whenPeriodHaveTwoInterval_whereSecondShorterThanFirst() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS - 1L); + for (long i = TS; i <= TS + 80000L; i += 10000L) { + saveEntries(deviceId, i); + } + saveEntries(deviceId, TS + 80000L + 1L); + + List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS + 80000, 50000, 1, Aggregation.COUNT, DESC_ORDER)); + + List entries = tsService.findAll(tenantId, deviceId, queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 4L)), entries.get(1)); + + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); + + entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 4L)), entries.get(1)); + } + + @Test + public void testFindByQuery_whenPeriodHaveTwoIntervalWithEqualsLength_whereNotAllEntriesInRange() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + for (long i = TS - 1L; i <= TS + 100000L + 1L; i += 10000) { + saveEntries(deviceId, i); + } + + List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS + 99999, 50000, 1, Aggregation.COUNT, DESC_ORDER)); + + List entries = tsService.findAll(tenantId, deviceId, queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); + + entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 75000, new LongDataEntry(LONG_KEY, 5L)), entries.get(1)); + } + + @Test + public void testFindByQuery_whenPeriodHaveTwoInterval_whereSecondShorterThanFirst_andNotAllEntriesInRange() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + for (long i = TS - 1L; i <= TS + 100000L + 1L; i += 10000L) { + saveEntries(deviceId, i); + } + + List queries = List.of(new BaseReadTsKvQuery(LONG_KEY, TS, TS + 80000, 50000, 1, Aggregation.COUNT, DESC_ORDER)); + + List entries = tsService.findAll(tenantId, deviceId, queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 3L)), entries.get(1)); + + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); + + entries = tsService.findAll(tenantId, entityView.getId(), queries).get(); + Assert.assertEquals(2, entries.size()); + Assert.assertEquals(toTsEntry(TS + 25000, new LongDataEntry(LONG_KEY, 5L)), entries.get(0)); + Assert.assertEquals(toTsEntry(TS + 65000, new LongDataEntry(LONG_KEY, 3L)), entries.get(1)); + } + @Test public void testFindByQueryDescOrder() throws Exception { DeviceId deviceId = new DeviceId(Uuids.timeBased()); @@ -472,6 +606,31 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue()); } + @Test + public void testSaveTs_RemoveTs_AndSaveTsAgain() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + + save(deviceId, 2000000L, 95); + save(deviceId, 4000000L, 100); + save(deviceId, 6000000L, 105); + List list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0L, + 8000000L, 200000, 3, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(3, list.size()); + + tsService.remove(tenantId, deviceId, Collections.singletonList( + new BaseDeleteTsKvQuery(LONG_KEY, 0L, 8000000L, false))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0L, + 8000000L, 200000, 3, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(0, list.size()); + + save(deviceId, 2000000L, 99); + save(deviceId, 4000000L, 104); + save(deviceId, 6000000L, 109); + list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0L, + 8000000L, 200000, 3, Aggregation.NONE))).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertEquals(3, list.size()); + } + private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception { TsKvEntry entry = new BasicTsKvEntry(ts, new LongDataEntry(LONG_KEY, value)); tsService.save(tenantId, deviceId, entry).get(MAX_TIMEOUT, TimeUnit.SECONDS); diff --git a/dao/src/test/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDaoTest.java new file mode 100644 index 0000000000..db216f7f6e --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDaoTest.java @@ -0,0 +1,156 @@ +/** + * 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.sqlts; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.TsKvEntry; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.BDDMockito.willCallRealMethod; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.id.TenantId.SYS_TENANT_ID; +import static org.thingsboard.server.common.data.kv.Aggregation.COUNT; + +public class AbstractChunkedAggregationTimeseriesDaoTest { + + final int LIMIT = 1; + final String TEMP = "temp"; + final String DESC = "DESC"; + AbstractChunkedAggregationTimeseriesDao tsDao; + + @Before + public void setUp() throws Exception { + tsDao = spy(AbstractChunkedAggregationTimeseriesDao.class); + ListenableFuture> optionalListenableFuture = Futures.immediateFuture(Optional.of(mock(TsKvEntry.class))); + willReturn(optionalListenableFuture).given(tsDao).findAndAggregateAsync(any(), anyString(), anyLong(), anyLong(), anyLong(), any()); + willReturn(Futures.immediateFuture(mock(TsKvEntry.class))).given(tsDao).getTskvEntriesFuture(any()); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenLastIntervalShorterThanOthersAndEqualsEndTs() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, 2000, LIMIT, COUNT, DESC); + ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 1, 2001, 1001, LIMIT, COUNT, DESC); + ReadTsKvQuery subQuerySecond = new BaseReadTsKvQuery(TEMP, 2001, 3001, 2501, LIMIT, COUNT, DESC); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(2)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 2001, getTsForReadTsKvQuery(1, 2001), COUNT); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQuerySecond.getKey(), 2001, 3000 + 1, getTsForReadTsKvQuery(2001, 3001), COUNT); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsPeriod() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, 3000, LIMIT, COUNT, DESC); + ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 1, 3001, 1501, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + assertThat(tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query)).isNotNull(); + verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3000 + 1, getTsForReadTsKvQuery(1, 3001), COUNT); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsPeriodMinusOne() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, 2999, LIMIT, COUNT, DESC); + ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 1, 3000, 1500, LIMIT, COUNT, DESC); + ReadTsKvQuery subQuerySecond = new BaseReadTsKvQuery(TEMP, 3000, 3001, 3000, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(2)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3000, getTsForReadTsKvQuery(1, 3000), COUNT); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQuerySecond.getKey(), 3000, 3001, getTsForReadTsKvQuery(3000, 3001), COUNT); + + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsPeriodPlusOne() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, 3001, LIMIT, COUNT, DESC); + ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 1, 3001, 1501, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3001, getTsForReadTsKvQuery(1, 3001), COUNT); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsOneMillisecondAndStartTsIsZero() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 0, 0, 1, LIMIT, COUNT, DESC); + ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 0, 1, 0, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 0, 1, getTsForReadTsKvQuery(0, 1), COUNT); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsOneMillisecondAndStartTsIsOne() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 1, 1, LIMIT, COUNT, DESC); + ReadTsKvQuery subQuery = new BaseReadTsKvQuery(TEMP, 1, 2, 1, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQuery.getKey(), 1, 2, getTsForReadTsKvQuery(1, 2), COUNT); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsOneMillisecondAndStartTsIsIntegerMax() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, Integer.MAX_VALUE, Integer.MAX_VALUE, 1, LIMIT, COUNT, DESC); + ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, Integer.MAX_VALUE, Integer.MAX_VALUE + 1L, Integer.MAX_VALUE, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), Integer.MAX_VALUE, 1L + Integer.MAX_VALUE, getTsForReadTsKvQuery(Integer.MAX_VALUE, 1L + Integer.MAX_VALUE), COUNT); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenIntervalEqualsBigNumber() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, Integer.MAX_VALUE, LIMIT, COUNT, DESC); + ReadTsKvQuery subQueryFirst = new BaseReadTsKvQuery(TEMP, 1, 3001, 1501, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(1)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, subQueryFirst.getKey(), 1, 3001, getTsForReadTsKvQuery(1, 3001), COUNT); + } + + @Test + public void givenIntervalNotMultiplePeriod_whenAggregateCount_thenCountIntervalEqualsPeriodSize() { + ReadTsKvQuery query = new BaseReadTsKvQuery(TEMP, 1, 3000, 3, LIMIT, COUNT, DESC); + willCallRealMethod().given(tsDao).findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + tsDao.findAllAsync(SYS_TENANT_ID, SYS_TENANT_ID, query); + verify(tsDao, times(1000)).findAndAggregateAsync(any(), any(), anyLong(), anyLong(), anyLong(), any()); + for (long i = 1; i <= 3000; i += 3) { + ReadTsKvQuery querySub = new BaseReadTsKvQuery(TEMP, i, i + 3, i + (i + 3 - i) / 2, LIMIT, COUNT, DESC); + verify(tsDao, times(1)).findAndAggregateAsync(SYS_TENANT_ID, querySub.getKey(), i, i + 3, getTsForReadTsKvQuery(i, i + 3), COUNT); + } + } + + long getTsForReadTsKvQuery(long startTs, long endTs) { + return startTs + (endTs - startTs) / 2L; + } + +} diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index 3ebcf5a7ec..27f82f331c 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -5,8 +5,6 @@ zk.zk_dir=/thingsboard updates.enabled=false audit-log.enabled=true -audit-log.by_tenant_partitioning=MONTHS -audit-log.default_query_period=30 audit-log.sink.type=none cache.type=caffeine @@ -35,6 +33,12 @@ cache.specs.entityViews.maxSize=100000 cache.specs.claimDevices.timeToLiveInMinutes=1440 cache.specs.claimDevices.maxSize=100000 +cache.specs.tenants.timeToLiveInMinutes=1440 +cache.specs.tenants.maxSize=100000 + +cache.specs.tenantsExist.timeToLiveInMinutes=1440 +cache.specs.tenantsExist.maxSize=100000 + cache.specs.securitySettings.timeToLiveInMinutes=1440 cache.specs.securitySettings.maxSize=100000 diff --git a/dao/src/test/resources/sql/system-data.sql b/dao/src/test/resources/sql/system-data.sql index 1d0d29aec5..bfa33c2284 100644 --- a/dao/src/test/resources/sql/system-data.sql +++ b/dao/src/test/resources/sql/system-data.sql @@ -26,13 +26,13 @@ VALUES ( '61441950-4612-11e7-a919-92ebcb67fe33', 1592576748000, '5a797660-4612-1 '$2a$10$5JTB8/hxWc9WAy62nCGSxeefl3KWmipA9nFpVdDa0/xfIseeBB4Bu' ); /** System settings **/ -INSERT INTO admin_settings ( id, created_time, key, json_value ) -VALUES ( '6a2266e4-4612-11e7-a919-92ebcb67fe33', 1592576748000, 'general', '{ +INSERT INTO admin_settings ( id, created_time, tenant_id, key, json_value ) +VALUES ( '6a2266e4-4612-11e7-a919-92ebcb67fe33', 1592576748000, '13814000-1dd2-11b2-8080-808080808080', 'general', '{ "baseUrl": "http://localhost:8080" }' ); -INSERT INTO admin_settings ( id, created_time, key, json_value ) -VALUES ( '6eaaefa6-4612-11e7-a919-92ebcb67fe33', 1592576748000, 'mail', '{ +INSERT INTO admin_settings ( id, created_time, tenant_id, key, json_value ) +VALUES ( '6eaaefa6-4612-11e7-a919-92ebcb67fe33', 1592576748000, '13814000-1dd2-11b2-8080-808080808080', 'mail', '{ "mailFrom": "Thingsboard ", "smtpProtocol": "smtp", "smtpHost": "localhost", diff --git a/docker/.env b/docker/.env index 11e44fdde8..f33ed50da5 100644 --- a/docker/.env +++ b/docker/.env @@ -1,5 +1,8 @@ TB_QUEUE_TYPE=kafka +# redis or redis-cluster +CACHE=redis + DOCKER_REPO=thingsboard JS_EXECUTOR_DOCKER_NAME=tb-js-executor @@ -10,6 +13,7 @@ HTTP_TRANSPORT_DOCKER_NAME=tb-http-transport COAP_TRANSPORT_DOCKER_NAME=tb-coap-transport LWM2M_TRANSPORT_DOCKER_NAME=tb-lwm2m-transport SNMP_TRANSPORT_DOCKER_NAME=tb-snmp-transport +TB_VC_EXECUTOR_DOCKER_NAME=tb-vc-executor TB_VERSION=latest @@ -21,4 +25,7 @@ DATABASE=postgres LOAD_BALANCER_NAME=haproxy-certbot # If enabled Prometheus and Grafana containers are deployed along with other containers -MONITORING_ENABLED=false \ No newline at end of file +MONITORING_ENABLED=false + +# Limit memory usage for each Java application +# JAVA_OPTS=-Xmx2048M -Xms2048M -Xss384k -XX:+AlwaysPreTouch diff --git a/docker/.gitignore b/docker/.gitignore index e5313d17cf..c9172ae6ce 100644 --- a/docker/.gitignore +++ b/docker/.gitignore @@ -5,5 +5,13 @@ tb-node/db/** tb-node/postgres/** tb-node/cassandra/** tb-transports/*/log -docker/tb-vc-executor/log/** +tb-vc-executor/log/** +tb-node/redis-cluster-data-0/** +tb-node/redis-cluster-data-1/** +tb-node/redis-cluster-data-2/** +tb-node/redis-cluster-data-3/** +tb-node/redis-cluster-data-4/** +tb-node/redis-cluster-data-5/** +tb-node/redis-data/** + !.env diff --git a/docker/README.md b/docker/README.md index 01f89ebb6c..71ea87f353 100644 --- a/docker/README.md +++ b/docker/README.md @@ -17,6 +17,13 @@ In order to set database type change the value of `DATABASE` variable in `.env` **NOTE**: According to the database type corresponding docker service will be deployed (see `docker-compose.postgres.yml`, `docker-compose.hybrid.yml` for details). +In order to set cache type change the value of `CACHE` variable in `.env` file to one of the following: + +- `redis` - use Redis standalone cache (1 node - 1 master); +- `redis-cluster` - use Redis cluster cache (6 nodes - 3 masters, 3 slaves); + +**NOTE**: According to the cache type corresponding docker service will be deployed (see `docker-compose.redis.yml`, `docker-compose.redis-cluster.yml` for details). + Execute the following command to create log folders for the services and chown of these folders to the docker container users. To be able to change user, **chown** command is used, which requires sudo permissions (script will request password for a sudo access): diff --git a/docker/cache-redis-cluster.env b/docker/cache-redis-cluster.env new file mode 100644 index 0000000000..a3b516063b --- /dev/null +++ b/docker/cache-redis-cluster.env @@ -0,0 +1,5 @@ +CACHE_TYPE=redis +REDIS_CONNECTION_TYPE=cluster +REDIS_NODES=redis-node-0:6379,redis-node-1:6379,redis-node-2:6379,redis-node-3:6379,redis-node-4:6379,redis-node-5:6379 +REDIS_USE_DEFAULT_POOL_CONFIG=false +REDIS_PASSWORD=thingsboard diff --git a/docker/cache-redis.env b/docker/cache-redis.env new file mode 100644 index 0000000000..7b92620666 --- /dev/null +++ b/docker/cache-redis.env @@ -0,0 +1,2 @@ +CACHE_TYPE=redis +REDIS_HOST=redis diff --git a/docker/compose-utils.sh b/docker/compose-utils.sh index 550f2dfea4..f45339eb92 100755 --- a/docker/compose-utils.sh +++ b/docker/compose-utils.sh @@ -26,7 +26,7 @@ function additionalComposeArgs() { ADDITIONAL_COMPOSE_ARGS="-f docker-compose.hybrid.yml" ;; *) - echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or hybrid." >&2 + echo "Unknown DATABASE value specified in the .env file: '${DATABASE}'. Should be either 'postgres' or 'hybrid'." >&2 exit 1 esac echo $ADDITIONAL_COMPOSE_ARGS @@ -55,7 +55,7 @@ function additionalComposeQueueArgs() { ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.service-bus.yml" ;; *) - echo "Unknown Queue service value specified: '${TB_QUEUE_TYPE}'. Should be either kafka or confluent or aws-sqs or pubsub or rabbitmq or service-bus." >&2 + echo "Unknown Queue service TB_QUEUE_TYPE value specified in the .env file: '${TB_QUEUE_TYPE}'. Should be either 'kafka' or 'confluent' or 'aws-sqs' or 'pubsub' or 'rabbitmq' or 'service-bus'." >&2 exit 1 esac echo $ADDITIONAL_COMPOSE_QUEUE_ARGS @@ -73,19 +73,125 @@ function additionalComposeMonitoringArgs() { fi } +function additionalComposeCacheArgs() { + source .env + CACHE_COMPOSE_ARGS="" + CACHE="${CACHE:-redis}" + case $CACHE in + redis) + CACHE_COMPOSE_ARGS="-f docker-compose.redis.yml" + ;; + redis-cluster) + CACHE_COMPOSE_ARGS="-f docker-compose.redis-cluster.yml" + ;; + *) + echo "Unknown CACHE value specified in the .env file: '${CACHE}'. Should be either 'redis' or 'redis-cluster'." >&2 + exit 1 + esac + echo $CACHE_COMPOSE_ARGS +} + function additionalStartupServices() { source .env ADDITIONAL_STARTUP_SERVICES="" case $DATABASE in postgres) - ADDITIONAL_STARTUP_SERVICES=postgres + ADDITIONAL_STARTUP_SERVICES="$ADDITIONAL_STARTUP_SERVICES postgres" ;; hybrid) - ADDITIONAL_STARTUP_SERVICES="postgres cassandra" + ADDITIONAL_STARTUP_SERVICES="$ADDITIONAL_STARTUP_SERVICES postgres cassandra" ;; *) - echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or hybrid." >&2 + echo "Unknown DATABASE value specified in the .env file: '${DATABASE}'. Should be either 'postgres' or 'hybrid'." >&2 exit 1 esac + + CACHE="${CACHE:-redis}" + case $CACHE in + redis) + ADDITIONAL_STARTUP_SERVICES="$ADDITIONAL_STARTUP_SERVICES redis" + ;; + redis-cluster) + ADDITIONAL_STARTUP_SERVICES="$ADDITIONAL_STARTUP_SERVICES redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5" + ;; + *) + echo "Unknown CACHE value specified in the .env file: '${CACHE}'. Should be either 'redis' or 'redis-cluster'." >&2 + exit 1 + esac + echo $ADDITIONAL_STARTUP_SERVICES } + +function permissionList() { + PERMISSION_LIST=" + 799 799 tb-node/log + 799 799 tb-transports/coap/log + 799 799 tb-transports/lwm2m/log + 799 799 tb-transports/http/log + 799 799 tb-transports/mqtt/log + 799 799 tb-transports/snmp/log + 799 799 tb-transports/coap/log + 799 799 tb-vc-executor/log + 999 999 tb-node/postgres + " + + source .env + + if [ "$DATABASE" = "hybrid" ]; then + PERMISSION_LIST="$PERMISSION_LIST + 999 999 tb-node/cassandra + " + fi + + CACHE="${CACHE:-redis}" + case $CACHE in + redis) + PERMISSION_LIST="$PERMISSION_LIST + 1001 1001 tb-node/redis-data + " + ;; + redis-cluster) + PERMISSION_LIST="$PERMISSION_LIST + 1001 1001 tb-node/redis-cluster-data-0 + 1001 1001 tb-node/redis-cluster-data-1 + 1001 1001 tb-node/redis-cluster-data-2 + 1001 1001 tb-node/redis-cluster-data-3 + 1001 1001 tb-node/redis-cluster-data-4 + 1001 1001 tb-node/redis-cluster-data-5 + " + ;; + *) + echo "Unknown CACHE value specified in the .env file: '${CACHE}'. Should be either 'redis' or 'redis-cluster'." >&2 + exit 1 + esac + + echo "$PERMISSION_LIST" +} + +function checkFolders() { + EXIT_CODE=0 + PERMISSION_LIST=$(permissionList) || exit $? + set -e + while read -r USR GRP DIR + do + if [ -z "$DIR" ]; then # skip empty lines + continue + fi + MESSAGE="Checking user ${USR} group ${GRP} dir ${DIR}" + if [[ -d "$DIR" ]] && + [[ $(ls -ldn "$DIR" | awk '{print $3}') -eq "$USR" ]] && + [[ $(ls -ldn "$DIR" | awk '{print $4}') -eq "$GRP" ]] + then + MESSAGE="$MESSAGE OK" + else + if [ "$1" = "--create" ]; then + echo "Create and chown: user ${USR} group ${GRP} dir ${DIR}" + mkdir -p "$DIR" && sudo chown -R "$USR":"$GRP" "$DIR" + else + echo "$MESSAGE FAILED" + EXIT_CODE=1 + fi + fi + done < <(echo "$PERMISSION_LIST") + return $EXIT_CODE +} diff --git a/docker/docker-check-log-folders.sh b/docker/docker-check-log-folders.sh new file mode 100755 index 0000000000..66e7ea394c --- /dev/null +++ b/docker/docker-check-log-folders.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# +# 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. +# + +set -e +source compose-utils.sh +checkFolders || exit $? +echo "OK" diff --git a/docker/docker-compose.aws-sqs.yml b/docker/docker-compose.aws-sqs.yml index e50d0d424f..331902851f 100644 --- a/docker/docker-compose.aws-sqs.yml +++ b/docker/docker-compose.aws-sqs.yml @@ -23,59 +23,39 @@ services: tb-core1: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper - - redis tb-core2: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper - - redis tb-rule-engine1: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper - - redis tb-rule-engine2: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper - - redis tb-mqtt-transport1: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper tb-mqtt-transport2: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper tb-http-transport1: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper tb-http-transport2: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper tb-coap-transport: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper tb-lwm2m-transport: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper tb-snmp-transport: env_file: - queue-aws-sqs.env - depends_on: - - zookeeper + tb-vc-executor1: + env_file: + - queue-aws-sqs.env + tb-vc-executor2: + env_file: + - queue-aws-sqs.env diff --git a/docker/docker-compose.cassandra.volumes.yml b/docker/docker-compose.cassandra.volumes.yml new file mode 100644 index 0000000000..37880b44fa --- /dev/null +++ b/docker/docker-compose.cassandra.volumes.yml @@ -0,0 +1,27 @@ +# +# 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. +# + +version: '2.2' + +services: + cassandra: + volumes: + - cassandra-volume:/var/lib/cassandra + +volumes: + cassandra-volume: + external: true + name: ${CASSANDRA_DATA_VOLUME} diff --git a/docker/docker-compose.confluent.yml b/docker/docker-compose.confluent.yml index 6983dd3d3e..077acdca98 100644 --- a/docker/docker-compose.confluent.yml +++ b/docker/docker-compose.confluent.yml @@ -23,23 +23,15 @@ services: tb-core1: env_file: - queue-confluent.env - depends_on: - - redis tb-core2: env_file: - queue-confluent.env - depends_on: - - redis tb-rule-engine1: env_file: - queue-confluent.env - depends_on: - - redis tb-rule-engine2: env_file: - queue-confluent.env - depends_on: - - redis tb-mqtt-transport1: env_file: - queue-confluent.env @@ -61,3 +53,9 @@ services: tb-snmp-transport: env_file: - queue-confluent.env + tb-vc-executor1: + env_file: + - queue-confluent.env + tb-vc-executor2: + env_file: + - queue-confluent.env diff --git a/docker/docker-compose.hybrid.yml b/docker/docker-compose.hybrid.yml index 7d7ab84c8f..12ea4cd099 100644 --- a/docker/docker-compose.hybrid.yml +++ b/docker/docker-compose.hybrid.yml @@ -29,7 +29,7 @@ services: - ./tb-node/postgres:/var/lib/postgresql/data cassandra: restart: always - image: "cassandra:3.11.3" + image: "cassandra:4.0.4" ports: - "9042" volumes: @@ -38,31 +38,23 @@ services: env_file: - tb-node.hybrid.env depends_on: - - zookeeper - - redis - postgres - cassandra tb-core2: env_file: - tb-node.hybrid.env depends_on: - - zookeeper - - redis - postgres - cassandra tb-rule-engine1: env_file: - tb-node.hybrid.env depends_on: - - zookeeper - - redis - postgres - cassandra tb-rule-engine2: env_file: - tb-node.hybrid.env depends_on: - - zookeeper - - redis - postgres - cassandra diff --git a/docker/docker-compose.kafka.yml b/docker/docker-compose.kafka.yml index 2184528e53..e6fa0c489f 100644 --- a/docker/docker-compose.kafka.yml +++ b/docker/docker-compose.kafka.yml @@ -19,7 +19,7 @@ version: '2.2' services: kafka: restart: always - image: "wurstmeister/kafka:2.13-2.6.0" + image: "bitnami/kafka:3.2.0" ports: - "9092:9092" env_file: @@ -36,25 +36,21 @@ services: - queue-kafka.env depends_on: - kafka - - redis tb-core2: env_file: - queue-kafka.env depends_on: - kafka - - redis tb-rule-engine1: env_file: - queue-kafka.env depends_on: - kafka - - redis tb-rule-engine2: env_file: - queue-kafka.env depends_on: - kafka - - redis tb-mqtt-transport1: env_file: - queue-kafka.env @@ -90,3 +86,13 @@ services: - queue-kafka.env depends_on: - kafka + tb-vc-executor1: + env_file: + - queue-kafka.env + depends_on: + - kafka + tb-vc-executor2: + env_file: + - queue-kafka.env + depends_on: + - kafka diff --git a/docker/docker-compose.postgres.volumes.yml b/docker/docker-compose.postgres.volumes.yml index 704e9400ae..caf78f23d7 100644 --- a/docker/docker-compose.postgres.volumes.yml +++ b/docker/docker-compose.postgres.volumes.yml @@ -20,59 +20,8 @@ services: postgres: volumes: - postgres-db-volume:/var/lib/postgresql/data - tb-core1: - volumes: - - tb-log-volume:/var/log/thingsboard - tb-core2: - volumes: - - tb-log-volume:/var/log/thingsboard - tb-rule-engine1: - volumes: - - tb-log-volume:/var/log/thingsboard - tb-rule-engine2: - volumes: - - tb-log-volume:/var/log/thingsboard - tb-coap-transport: - volumes: - - tb-coap-transport-log-volume:/var/log/tb-coap-transport - tb-lwm2m-transport: - volumes: - - tb-lwm2m-transport-log-volume:/var/log/tb-lwm2m-transport - tb-http-transport1: - volumes: - - tb-http-transport-log-volume:/var/log/tb-http-transport - tb-http-transport2: - volumes: - - tb-http-transport-log-volume:/var/log/tb-http-transport - tb-mqtt-transport1: - volumes: - - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport - tb-mqtt-transport2: - volumes: - - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport - tb-snmp-transport: - volumes: - - tb-snmp-transport-log-volume:/var/log/tb-snmp-transport volumes: postgres-db-volume: external: true name: ${POSTGRES_DATA_VOLUME} - tb-log-volume: - external: true - name: ${TB_LOG_VOLUME} - tb-coap-transport-log-volume: - external: true - name: ${TB_COAP_TRANSPORT_LOG_VOLUME} - tb-lwm2m-transport-log-volume: - external: true - name: ${TB_LWM2M_TRANSPORT_LOG_VOLUME} - tb-http-transport-log-volume: - external: true - name: ${TB_HTTP_TRANSPORT_LOG_VOLUME} - tb-mqtt-transport-log-volume: - external: true - name: ${TB_MQTT_TRANSPORT_LOG_VOLUME} - tb-snmp-transport-log-volume: - external: true - name: ${TB_SNMP_TRANSPORT_LOG_VOLUME} diff --git a/docker/docker-compose.postgres.yml b/docker/docker-compose.postgres.yml index 591ea59f7a..8fe8e6f53d 100644 --- a/docker/docker-compose.postgres.yml +++ b/docker/docker-compose.postgres.yml @@ -31,27 +31,19 @@ services: env_file: - tb-node.postgres.env depends_on: - - zookeeper - - redis - postgres tb-core2: env_file: - tb-node.postgres.env depends_on: - - zookeeper - - redis - postgres tb-rule-engine1: env_file: - tb-node.postgres.env depends_on: - - zookeeper - - redis - postgres tb-rule-engine2: env_file: - tb-node.postgres.env depends_on: - - zookeeper - - redis - postgres diff --git a/docker/docker-compose.pubsub.yml b/docker/docker-compose.pubsub.yml index 0364957ee6..7c122d0835 100644 --- a/docker/docker-compose.pubsub.yml +++ b/docker/docker-compose.pubsub.yml @@ -23,59 +23,39 @@ services: tb-core1: env_file: - queue-pubsub.env - depends_on: - - zookeeper - - redis tb-core2: env_file: - queue-pubsub.env - depends_on: - - zookeeper - - redis tb-rule-engine1: env_file: - queue-pubsub.env - depends_on: - - zookeeper - - redis tb-rule-engine2: env_file: - queue-pubsub.env - depends_on: - - zookeeper - - redis tb-mqtt-transport1: env_file: - queue-pubsub.env - depends_on: - - zookeeper tb-mqtt-transport2: env_file: - queue-pubsub.env - depends_on: - - zookeeper tb-http-transport1: env_file: - queue-pubsub.env - depends_on: - - zookeeper tb-http-transport2: env_file: - queue-pubsub.env - depends_on: - - zookeeper tb-coap-transport: env_file: - queue-pubsub.env - depends_on: - - zookeeper tb-lwm2m-transport: env_file: - queue-pubsub.env - depends_on: - - zookeeper tb-snmp-transport: env_file: - queue-pubsub.env - depends_on: - - zookeeper + tb-vc-executor1: + env_file: + - queue-pubsub.env + tb-vc-executor2: + env_file: + - queue-pubsub.env diff --git a/docker/docker-compose.rabbitmq.yml b/docker/docker-compose.rabbitmq.yml index 1eb37709e5..1f1cf1554c 100644 --- a/docker/docker-compose.rabbitmq.yml +++ b/docker/docker-compose.rabbitmq.yml @@ -23,59 +23,39 @@ services: tb-core1: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper - - redis tb-core2: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper - - redis tb-rule-engine1: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper - - redis tb-rule-engine2: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper - - redis tb-mqtt-transport1: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper tb-mqtt-transport2: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper tb-http-transport1: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper tb-http-transport2: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper tb-coap-transport: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper tb-lwm2m-transport: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper tb-snmp-transport: env_file: - queue-rabbitmq.env - depends_on: - - zookeeper + tb-vc-executor1: + env_file: + - queue-rabbitmq.env + tb-vc-executor2: + env_file: + - queue-rabbitmq.env diff --git a/docker/docker-compose.redis-cluster.volumes.yml b/docker/docker-compose.redis-cluster.volumes.yml new file mode 100644 index 0000000000..2cf319bd21 --- /dev/null +++ b/docker/docker-compose.redis-cluster.volumes.yml @@ -0,0 +1,58 @@ +# +# 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. +# + +version: '2.2' + +services: + # Redis cluster + redis-node-0: + volumes: + - redis-cluster-data-0:/bitnami/redis/data + redis-node-1: + volumes: + - redis-cluster-data-1:/bitnami/redis/data + redis-node-2: + volumes: + - redis-cluster-data-2:/bitnami/redis/data + redis-node-3: + volumes: + - redis-cluster-data-3:/bitnami/redis/data + redis-node-4: + volumes: + - redis-cluster-data-4:/bitnami/redis/data + redis-node-5: + volumes: + - redis-cluster-data-5:/bitnami/redis/data + +volumes: + redis-cluster-data-0: + external: true + name: ${REDIS_CLUSTER_DATA_VOLUME_0} + redis-cluster-data-1: + external: true + name: ${REDIS_CLUSTER_DATA_VOLUME_1} + redis-cluster-data-2: + external: true + name: ${REDIS_CLUSTER_DATA_VOLUME_2} + redis-cluster-data-3: + external: true + name: ${REDIS_CLUSTER_DATA_VOLUME_3} + redis-cluster-data-4: + external: true + name: ${REDIS_CLUSTER_DATA_VOLUME_4} + redis-cluster-data-5: + external: true + name: ${REDIS_CLUSTER_DATA_VOLUME_5} diff --git a/docker/docker-compose.redis-cluster.yml b/docker/docker-compose.redis-cluster.yml new file mode 100644 index 0000000000..9763b5d79a --- /dev/null +++ b/docker/docker-compose.redis-cluster.yml @@ -0,0 +1,156 @@ +# +# 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. +# + +version: '2.2' + +services: +# Redis cluster + redis-node-0: + image: bitnami/redis-cluster:7.0 + volumes: + - ./tb-node/redis-cluster-data-0:/bitnami/redis/data + environment: + - 'REDIS_PASSWORD=thingsboard' + - 'REDISCLI_AUTH=thingsboard' + - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5' + + redis-node-1: + image: bitnami/redis-cluster:7.0 + volumes: + - ./tb-node/redis-cluster-data-1:/bitnami/redis/data + depends_on: + - redis-node-0 + environment: + - 'REDIS_PASSWORD=thingsboard' + - 'REDISCLI_AUTH=thingsboard' + - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5' + + redis-node-2: + image: bitnami/redis-cluster:7.0 + volumes: + - ./tb-node/redis-cluster-data-2:/bitnami/redis/data + depends_on: + - redis-node-1 + environment: + - 'REDIS_PASSWORD=thingsboard' + - 'REDISCLI_AUTH=thingsboard' + - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5' + + redis-node-3: + image: bitnami/redis-cluster:7.0 + volumes: + - ./tb-node/redis-cluster-data-3:/bitnami/redis/data + depends_on: + - redis-node-2 + environment: + - 'REDIS_PASSWORD=thingsboard' + - 'REDISCLI_AUTH=thingsboard' + - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5' + + redis-node-4: + image: bitnami/redis-cluster:7.0 + volumes: + - ./tb-node/redis-cluster-data-4:/bitnami/redis/data + depends_on: + - redis-node-3 + environment: + - 'REDIS_PASSWORD=thingsboard' + - 'REDISCLI_AUTH=thingsboard' + - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5' + + redis-node-5: + image: bitnami/redis-cluster:7.0 + volumes: + - ./tb-node/redis-cluster-data-5:/bitnami/redis/data + depends_on: + - redis-node-0 + - redis-node-1 + - redis-node-2 + - redis-node-3 + - redis-node-4 + environment: + - 'REDIS_PASSWORD=thingsboard' + - 'REDISCLI_AUTH=thingsboard' + - 'REDIS_NODES=redis-node-0 redis-node-1 redis-node-2 redis-node-3 redis-node-4 redis-node-5' + - 'REDIS_CLUSTER_REPLICAS=1' + - 'REDIS_CLUSTER_CREATOR=yes' + +# ThingsBoard setup to use redis-cluster + tb-core1: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-core2: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-rule-engine1: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-rule-engine2: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-mqtt-transport1: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-mqtt-transport2: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-http-transport1: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-http-transport2: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-coap-transport: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-lwm2m-transport: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-snmp-transport: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-vc-executor1: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 + tb-vc-executor2: + env_file: + - cache-redis-cluster.env + depends_on: + - redis-node-5 diff --git a/docker/docker-compose.redis.volumes.yml b/docker/docker-compose.redis.volumes.yml new file mode 100644 index 0000000000..090aa441fe --- /dev/null +++ b/docker/docker-compose.redis.volumes.yml @@ -0,0 +1,27 @@ +# +# 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. +# + +version: '2.2' + +services: + redis: + volumes: + - redis-data:/bitnami/redis/data + +volumes: + redis-data: + external: true + name: ${REDIS_DATA_VOLUME} diff --git a/docker/docker-compose.redis.yml b/docker/docker-compose.redis.yml new file mode 100644 index 0000000000..e53a974134 --- /dev/null +++ b/docker/docker-compose.redis.yml @@ -0,0 +1,97 @@ +# +# 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. +# + +version: '2.2' + +services: +# Redis standalone + redis: + restart: always + image: bitnami/redis:7.0 + environment: + # ALLOW_EMPTY_PASSWORD is recommended only for development. + ALLOW_EMPTY_PASSWORD: "yes" + ports: + - '6379:6379' + volumes: + - ./tb-node/redis-data:/bitnami/redis/data + +# ThingsBoard setup to use redis-standalone + tb-core1: + env_file: + - cache-redis.env + depends_on: + - redis + tb-core2: + env_file: + - cache-redis.env + depends_on: + - redis + tb-rule-engine1: + env_file: + - cache-redis.env + depends_on: + - redis + tb-rule-engine2: + env_file: + - cache-redis.env + depends_on: + - redis + tb-mqtt-transport1: + env_file: + - cache-redis.env + depends_on: + - redis + tb-mqtt-transport2: + env_file: + - cache-redis.env + depends_on: + - redis + tb-http-transport1: + env_file: + - cache-redis.env + depends_on: + - redis + tb-http-transport2: + env_file: + - cache-redis.env + depends_on: + - redis + tb-coap-transport: + env_file: + - cache-redis.env + depends_on: + - redis + tb-lwm2m-transport: + env_file: + - cache-redis.env + depends_on: + - redis + tb-snmp-transport: + env_file: + - cache-redis.env + depends_on: + - redis + tb-vc-executor1: + env_file: + - cache-redis.env + depends_on: + - redis + tb-vc-executor2: + env_file: + - cache-redis.env + depends_on: + - redis diff --git a/docker/docker-compose.service-bus.yml b/docker/docker-compose.service-bus.yml index c511658b31..6e39de0baa 100644 --- a/docker/docker-compose.service-bus.yml +++ b/docker/docker-compose.service-bus.yml @@ -23,57 +23,39 @@ services: tb-core1: env_file: - queue-service-bus.env - depends_on: - - zookeeper - - redis tb-core2: env_file: - queue-service-bus.env - depends_on: - - zookeeper - - redis tb-rule-engine1: env_file: - queue-service-bus.env - depends_on: - - zookeeper - - redis tb-rule-engine2: env_file: - queue-service-bus.env - depends_on: - - zookeeper - - redis tb-mqtt-transport1: env_file: - queue-service-bus.env - depends_on: - - zookeeper tb-mqtt-transport2: env_file: - queue-service-bus.env - depends_on: - - zookeeper tb-http-transport1: env_file: - queue-service-bus.env - depends_on: - - zookeeper tb-http-transport2: env_file: - queue-service-bus.env - depends_on: - - zookeeper tb-coap-transport: env_file: - queue-service-bus.env tb-lwm2m-transport: env_file: - queue-service-bus.env - depends_on: - - zookeeper tb-snmp-transport: env_file: - queue-service-bus.env - depends_on: - - zookeeper + tb-vc-executor1: + env_file: + - queue-service-bus.env + tb-vc-executor2: + env_file: + - queue-service-bus.env diff --git a/docker/docker-compose.volumes.yml b/docker/docker-compose.volumes.yml new file mode 100644 index 0000000000..58269473e4 --- /dev/null +++ b/docker/docker-compose.volumes.yml @@ -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. +# + +version: '2.2' + +services: + tb-core1: + volumes: + - tb-log-volume:/var/log/thingsboard + tb-core2: + volumes: + - tb-log-volume:/var/log/thingsboard + tb-rule-engine1: + volumes: + - tb-log-volume:/var/log/thingsboard + tb-rule-engine2: + volumes: + - tb-log-volume:/var/log/thingsboard + tb-coap-transport: + volumes: + - tb-coap-transport-log-volume:/var/log/tb-coap-transport + tb-lwm2m-transport: + volumes: + - tb-lwm2m-transport-log-volume:/var/log/tb-lwm2m-transport + tb-http-transport1: + volumes: + - tb-http-transport-log-volume:/var/log/tb-http-transport + tb-http-transport2: + volumes: + - tb-http-transport-log-volume:/var/log/tb-http-transport + tb-mqtt-transport1: + volumes: + - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport + tb-mqtt-transport2: + volumes: + - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport + tb-snmp-transport: + volumes: + - tb-snmp-transport-log-volume:/var/log/tb-snmp-transport + tb-vc-executor1: + volumes: + - tb-vc-executor-log-volume:/var/log/tb-vc-executor + tb-vc-executor2: + volumes: + - tb-vc-executor-log-volume:/var/log/tb-vc-executor + +volumes: + tb-log-volume: + external: true + name: ${TB_LOG_VOLUME} + tb-coap-transport-log-volume: + external: true + name: ${TB_COAP_TRANSPORT_LOG_VOLUME} + tb-lwm2m-transport-log-volume: + external: true + name: ${TB_LWM2M_TRANSPORT_LOG_VOLUME} + tb-http-transport-log-volume: + external: true + name: ${TB_HTTP_TRANSPORT_LOG_VOLUME} + tb-mqtt-transport-log-volume: + external: true + name: ${TB_MQTT_TRANSPORT_LOG_VOLUME} + tb-snmp-transport-log-volume: + external: true + name: ${TB_SNMP_TRANSPORT_LOG_VOLUME} + tb-vc-executor-log-volume: + external: true + name: ${TB_VC_EXECUTOR_LOG_VOLUME} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 82fbaeed74..6ec410fcde 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -20,21 +20,17 @@ version: '2.2' services: zookeeper: restart: always - image: "zookeeper:3.5" + image: "zookeeper:3.8.0" ports: - "2181" environment: ZOO_MY_ID: 1 ZOO_SERVERS: server.1=zookeeper:2888:3888;zookeeper:2181 - redis: - restart: always - image: redis:4.0 - ports: - - "6379" + ZOO_ADMINSERVER_ENABLED: "false" tb-js-executor: restart: always image: "${DOCKER_REPO}/${JS_EXECUTOR_DOCKER_NAME}:${TB_VERSION}" - scale: 20 + scale: 10 env_file: - tb-js-executor.env tb-core1: @@ -52,6 +48,7 @@ services: TB_SERVICE_ID: tb-core1 TB_SERVICE_TYPE: tb-core EDGES_ENABLED: "true" + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-node.env volumes: @@ -59,7 +56,6 @@ services: - ./tb-node/log:/var/log/thingsboard depends_on: - zookeeper - - redis - tb-js-executor - tb-rule-engine1 - tb-rule-engine2 @@ -78,6 +74,7 @@ services: TB_SERVICE_ID: tb-core2 TB_SERVICE_TYPE: tb-core EDGES_ENABLED: "true" + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-node.env volumes: @@ -85,7 +82,6 @@ services: - ./tb-node/log:/var/log/thingsboard depends_on: - zookeeper - - redis - tb-js-executor - tb-rule-engine1 - tb-rule-engine2 @@ -102,6 +98,7 @@ services: environment: TB_SERVICE_ID: tb-rule-engine1 TB_SERVICE_TYPE: tb-rule-engine + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-node.env volumes: @@ -109,7 +106,6 @@ services: - ./tb-node/log:/var/log/thingsboard depends_on: - zookeeper - - redis - tb-js-executor tb-rule-engine2: restart: always @@ -124,6 +120,7 @@ services: environment: TB_SERVICE_ID: tb-rule-engine2 TB_SERVICE_TYPE: tb-rule-engine + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-node.env volumes: @@ -131,7 +128,6 @@ services: - ./tb-node/log:/var/log/thingsboard depends_on: - zookeeper - - redis - tb-js-executor tb-mqtt-transport1: restart: always @@ -140,6 +136,7 @@ services: - "1883" environment: TB_SERVICE_ID: tb-mqtt-transport1 + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-mqtt-transport.env volumes: @@ -156,6 +153,7 @@ services: - "1883" environment: TB_SERVICE_ID: tb-mqtt-transport2 + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-mqtt-transport.env volumes: @@ -172,6 +170,7 @@ services: - "8081" environment: TB_SERVICE_ID: tb-http-transport1 + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-http-transport.env volumes: @@ -188,6 +187,7 @@ services: - "8081" environment: TB_SERVICE_ID: tb-http-transport2 + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-http-transport.env volumes: @@ -204,6 +204,7 @@ services: - "5683:5683/udp" environment: TB_SERVICE_ID: tb-coap-transport + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-coap-transport.env volumes: @@ -220,6 +221,7 @@ services: - "5685:5685/udp" environment: TB_SERVICE_ID: tb-lwm2m-transport + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-lwm2m-transport.env volumes: @@ -234,6 +236,7 @@ services: image: "${DOCKER_REPO}/${SNMP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" environment: TB_SERVICE_ID: tb-snmp-transport + JAVA_OPTS: "${JAVA_OPTS}" env_file: - tb-snmp-transport.env volumes: @@ -257,6 +260,40 @@ services: - "8080" env_file: - tb-web-ui.env + tb-vc-executor1: + restart: always + image: "${DOCKER_REPO}/${TB_VC_EXECUTOR_DOCKER_NAME}:${TB_VERSION}" + ports: + - "8081" + environment: + TB_SERVICE_ID: tb-vc-executor1 + JAVA_OPTS: "${JAVA_OPTS}" + env_file: + - tb-vc-executor.env + volumes: + - ./tb-vc-executor/conf:/config + - ./tb-vc-executor/log:/var/log/tb-vc-executor + depends_on: + - zookeeper + - tb-core1 + - tb-core2 + tb-vc-executor2: + restart: always + image: "${DOCKER_REPO}/${TB_VC_EXECUTOR_DOCKER_NAME}:${TB_VERSION}" + ports: + - "8081" + environment: + TB_SERVICE_ID: tb-vc-executor2 + JAVA_OPTS: "${JAVA_OPTS}" + env_file: + - tb-vc-executor.env + volumes: + - ./tb-vc-executor/conf:/config + - ./tb-vc-executor/log:/var/log/tb-vc-executor + depends_on: + - zookeeper + - tb-core1 + - tb-core2 haproxy: restart: always container_name: "${LOAD_BALANCER_NAME}" diff --git a/docker/docker-create-log-folders.sh b/docker/docker-create-log-folders.sh index 5af0f96377..54a74f4812 100755 --- a/docker/docker-create-log-folders.sh +++ b/docker/docker-create-log-folders.sh @@ -15,14 +15,6 @@ # limitations under the License. # -mkdir -p tb-node/log/ && sudo chown -R 799:799 tb-node/log/ - -mkdir -p tb-transports/coap/log && sudo chown -R 799:799 tb-transports/coap/log - -mkdir -p tb-transports/lwm2m/log && sudo chown -R 799:799 tb-transports/lwm2m/log - -mkdir -p tb-transports/http/log && sudo chown -R 799:799 tb-transports/http/log - -mkdir -p tb-transports/mqtt/log && sudo chown -R 799:799 tb-transports/mqtt/log - -mkdir -p tb-transports/snmp/log && sudo chown -R 799:799 tb-transports/snmp/log +set -e +source compose-utils.sh +checkFolders --create diff --git a/docker/docker-install-tb.sh b/docker/docker-install-tb.sh index 6f6b1e9511..628b16e6ee 100755 --- a/docker/docker-install-tb.sh +++ b/docker/docker-install-tb.sh @@ -45,12 +45,21 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? +ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? + ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? +checkFolders --create || exit $? + if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then - docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES + docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ + up -d $ADDITIONAL_STARTUP_SERVICES fi -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} tb-core1 +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ + run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} \ + tb-core1 diff --git a/docker/docker-remove-services.sh b/docker/docker-remove-services.sh index 89f0f7844c..769150c1f3 100755 --- a/docker/docker-remove-services.sh +++ b/docker/docker-remove-services.sh @@ -23,6 +23,10 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? +ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? + ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS down -v +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \ + down -v diff --git a/docker/docker-start-services.sh b/docker/docker-start-services.sh index 9f159774d8..1a06946308 100755 --- a/docker/docker-start-services.sh +++ b/docker/docker-start-services.sh @@ -23,6 +23,12 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? +ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? + ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS up -d +checkFolders --create || exit $? + +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \ + up -d diff --git a/docker/docker-stop-services.sh b/docker/docker-stop-services.sh index 61e68d6dd5..5b09aea204 100755 --- a/docker/docker-stop-services.sh +++ b/docker/docker-stop-services.sh @@ -23,6 +23,10 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? +ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? + ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS stop +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \ + stop diff --git a/docker/docker-update-service.sh b/docker/docker-update-service.sh index 739fcf6543..027280635d 100755 --- a/docker/docker-update-service.sh +++ b/docker/docker-update-service.sh @@ -23,5 +23,11 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS pull $@ -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d --no-deps --build $@ +ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? + +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ + pull $@ +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ + up -d --no-deps --build $@ diff --git a/docker/docker-upgrade-tb.sh b/docker/docker-upgrade-tb.sh index 3b1415a965..87dff9da15 100755 --- a/docker/docker-upgrade-tb.sh +++ b/docker/docker-upgrade-tb.sh @@ -44,10 +44,22 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? +ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? + ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS pull tb-core1 +checkFolders --create || exit $? + +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ + pull \ + tb-core1 -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ + up -d $ADDITIONAL_STARTUP_SERVICES -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} tb-core1 +docker-compose \ + -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ + run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} \ + tb-core1 diff --git a/docker/kafka.env b/docker/kafka.env index bd23f99ad1..9c28885252 100644 --- a/docker/kafka.env +++ b/docker/kafka.env @@ -1,12 +1,11 @@ - -KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 -KAFKA_LISTENERS=INSIDE://:9093,OUTSIDE://:9092 -KAFKA_ADVERTISED_LISTENERS=INSIDE://:9093,OUTSIDE://kafka:9092 -KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT -KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE -KAFKA_CREATE_TOPICS=js_eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb_transport.api.requests:3:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600 -KAFKA_AUTO_CREATE_TOPICS_ENABLE=false -KAFKA_LOG_RETENTION_BYTES=1073741824 -KAFKA_LOG_SEGMENT_BYTES=268435456 -KAFKA_LOG_RETENTION_MS=300000 -KAFKA_LOG_CLEANUP_POLICY=delete +ALLOW_PLAINTEXT_LISTENER=yes +KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 +KAFKA_CFG_LISTENERS=INSIDE://:9093,OUTSIDE://:9092 +KAFKA_CFG_ADVERTISED_LISTENERS=INSIDE://:9093,OUTSIDE://kafka:9092 +KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT +KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE=false +KAFKA_CFG_INTER_BROKER_LISTENER_NAME=INSIDE +KAFKA_CFG_LOG_RETENTION_BYTES=1073741824 +KAFKA_CFG_SEGMENT_BYTES=268435456 +KAFKA_CFG_LOG_RETENTION_MS=300000 +KAFKA_CFG_LOG_CLEANUP_POLICY=delete diff --git a/docker/tb-coap-transport.env b/docker/tb-coap-transport.env index 9e6a41c930..079443c98b 100644 --- a/docker/tb-coap-transport.env +++ b/docker/tb-coap-transport.env @@ -10,6 +10,3 @@ METRICS_ENDPOINTS_EXPOSE=prometheus WEB_APPLICATION_ENABLE=true WEB_APPLICATION_TYPE=servlet HTTP_BIND_PORT=8081 - -CACHE_TYPE=redis -REDIS_HOST=redis diff --git a/docker/tb-http-transport.env b/docker/tb-http-transport.env index 1b4ce7a298..7e0679987f 100644 --- a/docker/tb-http-transport.env +++ b/docker/tb-http-transport.env @@ -7,6 +7,3 @@ HTTP_REQUEST_TIMEOUT=60000 METRICS_ENABLED=true METRICS_ENDPOINTS_EXPOSE=prometheus - -CACHE_TYPE=redis -REDIS_HOST=redis diff --git a/docker/tb-lwm2m-transport.env b/docker/tb-lwm2m-transport.env index 4616d45972..f284803a46 100644 --- a/docker/tb-lwm2m-transport.env +++ b/docker/tb-lwm2m-transport.env @@ -10,6 +10,3 @@ METRICS_ENDPOINTS_EXPOSE=prometheus WEB_APPLICATION_ENABLE=true WEB_APPLICATION_TYPE=servlet HTTP_BIND_PORT=8081 - -CACHE_TYPE=redis -REDIS_HOST=redis diff --git a/docker/tb-mqtt-transport.env b/docker/tb-mqtt-transport.env index 0cd51c7371..e38cb2124a 100644 --- a/docker/tb-mqtt-transport.env +++ b/docker/tb-mqtt-transport.env @@ -10,6 +10,3 @@ METRICS_ENDPOINTS_EXPOSE=prometheus WEB_APPLICATION_ENABLE=true WEB_APPLICATION_TYPE=servlet HTTP_BIND_PORT=8081 - -CACHE_TYPE=redis -REDIS_HOST=redis diff --git a/docker/tb-node.env b/docker/tb-node.env index e3393d41b1..ba66757ecc 100644 --- a/docker/tb-node.env +++ b/docker/tb-node.env @@ -4,8 +4,6 @@ ZOOKEEPER_ENABLED=true ZOOKEEPER_URL=zookeeper:2181 JS_EVALUATOR=remote TRANSPORT_TYPE=remote -CACHE_TYPE=redis -REDIS_HOST=redis HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false diff --git a/docker/tb-node/conf/logback.xml b/docker/tb-node/conf/logback.xml index bc694d704d..c17e8a43cf 100644 --- a/docker/tb-node/conf/logback.xml +++ b/docker/tb-node/conf/logback.xml @@ -42,6 +42,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docker/tb-snmp-transport.env b/docker/tb-snmp-transport.env index 4851e9f6c1..e2cc39d658 100644 --- a/docker/tb-snmp-transport.env +++ b/docker/tb-snmp-transport.env @@ -6,6 +6,3 @@ METRICS_ENDPOINTS_EXPOSE=prometheus WEB_APPLICATION_ENABLE=true WEB_APPLICATION_TYPE=servlet HTTP_BIND_PORT=8081 - -CACHE_TYPE=redis -REDIS_HOST=redis diff --git a/docker/tb-transports/coap/conf/logback.xml b/docker/tb-transports/coap/conf/logback.xml index 1a3e8ef6b3..65d13a55cf 100644 --- a/docker/tb-transports/coap/conf/logback.xml +++ b/docker/tb-transports/coap/conf/logback.xml @@ -42,6 +42,11 @@ + + + + + diff --git a/docker/tb-transports/http/conf/logback.xml b/docker/tb-transports/http/conf/logback.xml index 05361fe680..a3ae374bb7 100644 --- a/docker/tb-transports/http/conf/logback.xml +++ b/docker/tb-transports/http/conf/logback.xml @@ -42,6 +42,11 @@ + + + + + diff --git a/docker/tb-transports/lwm2m/conf/logback.xml b/docker/tb-transports/lwm2m/conf/logback.xml index d2485675ee..5546167f3d 100644 --- a/docker/tb-transports/lwm2m/conf/logback.xml +++ b/docker/tb-transports/lwm2m/conf/logback.xml @@ -42,6 +42,10 @@ + + + + diff --git a/docker/tb-transports/mqtt/conf/logback.xml b/docker/tb-transports/mqtt/conf/logback.xml index 09bcaea84c..d4e7b811f8 100644 --- a/docker/tb-transports/mqtt/conf/logback.xml +++ b/docker/tb-transports/mqtt/conf/logback.xml @@ -42,6 +42,10 @@ + + + + diff --git a/docker/tb-transports/snmp/conf/logback.xml b/docker/tb-transports/snmp/conf/logback.xml index dc53ddd8a3..0b6fbc726b 100644 --- a/docker/tb-transports/snmp/conf/logback.xml +++ b/docker/tb-transports/snmp/conf/logback.xml @@ -42,6 +42,10 @@ + + + + diff --git a/docker/tb-vc-executor.env b/docker/tb-vc-executor.env new file mode 100644 index 0000000000..f92e30b78f --- /dev/null +++ b/docker/tb-vc-executor.env @@ -0,0 +1,2 @@ +ZOOKEEPER_ENABLED=true +ZOOKEEPER_URL=zookeeper:2181 diff --git a/docker/tb-vc-executor/conf/logback.xml b/docker/tb-vc-executor/conf/logback.xml new file mode 100644 index 0000000000..ebde7cbd69 --- /dev/null +++ b/docker/tb-vc-executor/conf/logback.xml @@ -0,0 +1,54 @@ + + + + + + + /var/log/tb-vc-executor/${TB_SERVICE_ID}/tb-vc-executor.log + + /var/log/tb-vc-executor/${TB_SERVICE_ID}/tb-vc-executor.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + + + + diff --git a/docker/tb-vc-executor/conf/tb-vc-executor.conf b/docker/tb-vc-executor/conf/tb-vc-executor.conf new file mode 100644 index 0000000000..f140e3fb76 --- /dev/null +++ b/docker/tb-vc-executor/conf/tb-vc-executor.conf @@ -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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-vc-executor/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-vc-executor/${TB_SERVICE_ID}-heapdump.bin" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" +export LOG_FILENAME=tb-vc-executor.out +export LOADER_PATH=/usr/share/tb-vc-executor/conf diff --git a/msa/black-box-tests/README.md b/msa/black-box-tests/README.md index 9c68789e67..a45badf44a 100644 --- a/msa/black-box-tests/README.md +++ b/msa/black-box-tests/README.md @@ -18,8 +18,17 @@ As result, in REPOSITORY column, next images should be present: thingsboard/tb-web-ui thingsboard/tb-js-executor -- Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory: +- Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory with Redis standalone: mvn clean install -DblackBoxTests.skip=false +- Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory with Redis cluster: + + mvn clean install -DblackBoxTests.skip=false -DblackBoxTests.redisCluster=true + +- Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory in Hybrid mode (postgres + cassandra): + + mvn clean install -DblackBoxTests.skip=false -DblackBoxTests.hybridMode=true + + diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java index 5ea04de462..bfe1c913eb 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -69,6 +69,9 @@ public abstract class AbstractContainerTest { protected static final String WSS_URL = "wss://localhost"; protected static String TB_TOKEN; protected static RestClient restClient; + + protected static long timeoutMultiplier = 1; + protected ObjectMapper mapper = new ObjectMapper(); protected JsonParser jsonParser = new JsonParser(); @@ -76,6 +79,10 @@ public abstract class AbstractContainerTest { public static void before() throws Exception { restClient = new RestClient(HTTPS_URL); restClient.getRestTemplate().setRequestFactory(getRequestFactoryForSelfSignedCert()); + + if (!"kafka".equals(System.getProperty("blackBoxTests.queue", "kafka"))) { + timeoutMultiplier = 10; + } } @Rule @@ -124,7 +131,7 @@ public abstract class AbstractContainerTest { } protected WsClient subscribeToWebSocket(DeviceId deviceId, String scope, CmdsType property) throws Exception { - WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken())); + WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken()), timeoutMultiplier); SSLContextBuilder builder = SSLContexts.custom(); builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); wsClient.setSocketFactory(builder.build().getSocketFactory()); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index f60d6d7545..47acbde16e 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -22,11 +22,19 @@ import org.junit.extensions.cpsuite.ClasspathSuite; import org.junit.runner.RunWith; import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.wait.strategy.Wait; +import org.thingsboard.server.common.data.StringUtils; import java.io.File; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.UUID; import static org.hamcrest.CoreMatchers.containsString; @@ -39,10 +47,15 @@ import static org.junit.Assert.fail; @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) @Slf4j public class ContainerTestSuite { - + final static boolean IS_REDIS_CLUSTER = Boolean.parseBoolean(System.getProperty("blackBoxTests.redisCluster")); + final static boolean IS_HYBRID_MODE = Boolean.parseBoolean(System.getProperty("blackBoxTests.hybridMode")); + final static String QUEUE_TYPE = System.getProperty("blackBoxTests.queue", "kafka"); private static final String SOURCE_DIR = "./../../docker/"; private static final String TB_CORE_LOG_REGEXP = ".*Starting polling for events.*"; private static final String TRANSPORTS_LOG_REGEXP = ".*Going to recalculate partitions.*"; + private static final String TB_VC_LOG_REGEXP = TRANSPORTS_LOG_REGEXP; + private static final String TB_JS_EXECUTOR_LOG_REGEXP = ".*template started.*"; + private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(400); private static DockerComposeContainer testContainer; @@ -52,6 +65,8 @@ public class ContainerTestSuite { @ClassRule public static DockerComposeContainer getTestContainer() { if (testContainer == null) { + log.info("System property of blackBoxTests.redisCluster is {}", IS_REDIS_CLUSTER); + log.info("System property of blackBoxTests.hybridMode is {}", IS_HYBRID_MODE); boolean skipTailChildContainers = Boolean.valueOf(System.getProperty("blackBoxTests.skipTailChildContainers")); try { final String targetDir = FileUtils.getTempDirectoryPath() + "/" + "ContainerTestSuite-" + UUID.randomUUID() + "/"; @@ -59,8 +74,10 @@ public class ContainerTestSuite { FileUtils.copyDirectory(new File(SOURCE_DIR), new File(targetDir)); replaceInFile(targetDir + "docker-compose.yml", " container_name: \"${LOAD_BALANCER_NAME}\"", "", "container_name"); + FileUtils.copyDirectory(new File("src/test/resources"), new File(targetDir)); + class DockerComposeContainerImpl> extends DockerComposeContainer { - public DockerComposeContainerImpl(File... composeFiles) { + public DockerComposeContainerImpl(List composeFiles) { super(composeFiles); } @@ -71,23 +88,70 @@ public class ContainerTestSuite { } } - testContainer = new DockerComposeContainerImpl<>( + List composeFiles = new ArrayList<>(Arrays.asList( new File(targetDir + "docker-compose.yml"), - new File(targetDir + "docker-compose.postgres.yml"), + new File(targetDir + "docker-compose.volumes.yml"), + new File(targetDir + (IS_HYBRID_MODE ? "docker-compose.hybrid.yml" : "docker-compose.postgres.yml")), new File(targetDir + "docker-compose.postgres.volumes.yml"), - new File(targetDir + "docker-compose.kafka.yml")) + new File(targetDir + "docker-compose." + QUEUE_TYPE + ".yml"), + new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.yml" : "docker-compose.redis.yml")), + new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.volumes.yml" : "docker-compose.redis.volumes.yml")) + )); + + Map queueEnv = new HashMap<>(); + queueEnv.put("TB_QUEUE_TYPE", QUEUE_TYPE); + switch (QUEUE_TYPE) { + case "kafka": + composeFiles.add(new File(targetDir + "docker-compose.kafka.yml")); + break; + case "aws-sqs": + replaceInFile(targetDir, "queue-aws-sqs.env", + Map.of("YOUR_KEY", getSysProp("blackBoxTests.awsKey"), + "YOUR_SECRET", getSysProp("blackBoxTests.awsSecret"), + "YOUR_REGION", getSysProp("blackBoxTests.awsRegion"))); + break; + case "rabbitmq": + composeFiles.add(new File(targetDir + "docker-compose.rabbitmq-server.yml")); + replaceInFile(targetDir, "queue-rabbitmq.env", + Map.of("localhost", "rabbitmq")); + break; + case "service-bus": + replaceInFile(targetDir, "queue-service-bus.env", + Map.of("YOUR_NAMESPACE_NAME", getSysProp("blackBoxTests.serviceBusNamespace"), + "YOUR_SAS_KEY_NAME", getSysProp("blackBoxTests.serviceBusSASPolicy"))); + replaceInFile(targetDir, "queue-service-bus.env", + Map.of("YOUR_SAS_KEY", getSysProp("blackBoxTests.serviceBusPrimaryKey"))); + break; + case "pubsub": + replaceInFile(targetDir, "queue-pubsub.env", + Map.of("YOUR_PROJECT_ID", getSysProp("blackBoxTests.pubSubProjectId"), + "YOUR_SERVICE_ACCOUNT", getSysProp("blackBoxTests.pubSubServiceAccount"))); + break; + default: + throw new RuntimeException("Unsupported queue type: " + QUEUE_TYPE); + } + + if (IS_HYBRID_MODE) { + composeFiles.add(new File(targetDir + "docker-compose.cassandra.volumes.yml")); + } + + testContainer = new DockerComposeContainerImpl<>(composeFiles) .withPull(false) .withLocalCompose(true) .withTailChildContainers(!skipTailChildContainers) .withEnv(installTb.getEnv()) + .withEnv(queueEnv) .withEnv("LOAD_BALANCER_NAME", "") - .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(400))) - .waitingFor("tb-core1", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400))) - .waitingFor("tb-core2", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400))) - .waitingFor("tb-http-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400))) - .waitingFor("tb-http-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400))) - .waitingFor("tb-mqtt-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400))) - .waitingFor("tb-mqtt-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(Duration.ofSeconds(400))); + .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-core1", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-core2", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-http-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-http-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-mqtt-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-mqtt-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-vc-executor1", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-vc-executor2", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-js-executor", Wait.forLogMessage(TB_JS_EXECUTOR_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)); } catch (Exception e) { log.error("Failed to create test container", e); fail("Failed to create test container"); @@ -96,6 +160,23 @@ public class ContainerTestSuite { return testContainer; } + private static void replaceInFile(String targetDir, String fileName, Map replacements) throws IOException { + Path envFilePath = Path.of(targetDir, fileName); + String data = Files.readString(envFilePath); + for (var entry : replacements.entrySet()) { + data = data.replace(entry.getKey(), entry.getValue()); + } + Files.write(envFilePath, data.getBytes(StandardCharsets.UTF_8)); + } + + private static String getSysProp(String propertyName) { + var value = System.getProperty(propertyName); + if (StringUtils.isEmpty(value)) { + throw new RuntimeException("Please define system property: " + propertyName + "!"); + } + return value; + } + private static void tryDeleteDir(String targetDir) { try { log.info("Trying to delete temp dir {}", targetDir); @@ -111,7 +192,7 @@ public class ContainerTestSuite { * docker-compose files which contain container_name are not supported and the creation of DockerComposeContainer fails due to IllegalStateException. * This has been introduced in #1151 as a quick fix for unintuitive feedback. https://github.com/testcontainers/testcontainers-java/issues/1151 * Using the latest testcontainers and waiting for the fix... - * */ + */ private static void replaceInFile(String sourceFilename, String target, String replacement, String verifyPhrase) { try { File file = new File(sourceFilename); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java index 417799f64f..eedf070fac 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -15,63 +15,113 @@ */ package org.thingsboard.server.msa; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.junit.rules.ExternalResource; import org.testcontainers.utility.Base58; import java.io.File; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +@Slf4j public class ThingsBoardDbInstaller extends ExternalResource { + final static boolean IS_REDIS_CLUSTER = Boolean.parseBoolean(System.getProperty("blackBoxTests.redisCluster")); + final static boolean IS_HYBRID_MODE = Boolean.parseBoolean(System.getProperty("blackBoxTests.hybridMode")); private final static String POSTGRES_DATA_VOLUME = "tb-postgres-test-data-volume"; + + private final static String CASSANDRA_DATA_VOLUME = "tb-cassandra-test-data-volume"; + private final static String REDIS_DATA_VOLUME = "tb-redis-data-volume"; + private final static String REDIS_CLUSTER_DATA_VOLUME = "tb-redis-cluster-data-volume"; private final static String TB_LOG_VOLUME = "tb-log-test-volume"; private final static String TB_COAP_TRANSPORT_LOG_VOLUME = "tb-coap-transport-log-test-volume"; private final static String TB_LWM2M_TRANSPORT_LOG_VOLUME = "tb-lwm2m-transport-log-test-volume"; private final static String TB_HTTP_TRANSPORT_LOG_VOLUME = "tb-http-transport-log-test-volume"; private final static String TB_MQTT_TRANSPORT_LOG_VOLUME = "tb-mqtt-transport-log-test-volume"; private final static String TB_SNMP_TRANSPORT_LOG_VOLUME = "tb-snmp-transport-log-test-volume"; + private final static String TB_VC_EXECUTOR_LOG_VOLUME = "tb-vc-executor-log-test-volume"; + private final static String JAVA_OPTS = "-Xmx512m"; private final DockerComposeExecutor dockerCompose; private final String postgresDataVolume; + private final String cassandraDataVolume; + + private final String redisDataVolume; + private final String redisClusterDataVolume; private final String tbLogVolume; private final String tbCoapTransportLogVolume; private final String tbLwm2mTransportLogVolume; private final String tbHttpTransportLogVolume; private final String tbMqttTransportLogVolume; private final String tbSnmpTransportLogVolume; + private final String tbVcExecutorLogVolume; private final Map env; public ThingsBoardDbInstaller() { - List composeFiles = Arrays.asList(new File("./../../docker/docker-compose.yml"), - new File("./../../docker/docker-compose.postgres.yml"), - new File("./../../docker/docker-compose.postgres.volumes.yml")); + log.info("System property of blackBoxTests.redisCluster is {}", IS_REDIS_CLUSTER); + log.info("System property of blackBoxTests.hybridMode is {}", IS_HYBRID_MODE); + List composeFiles = new ArrayList<>(Arrays.asList( + new File("./../../docker/docker-compose.yml"), + new File("./../../docker/docker-compose.volumes.yml"), + IS_HYBRID_MODE + ? new File("./../../docker/docker-compose.hybrid.yml") + : new File("./../../docker/docker-compose.postgres.yml"), + new File("./../../docker/docker-compose.postgres.volumes.yml"), + IS_REDIS_CLUSTER + ? new File("./../../docker/docker-compose.redis-cluster.yml") + : new File("./../../docker/docker-compose.redis.yml"), + IS_REDIS_CLUSTER + ? new File("./../../docker/docker-compose.redis-cluster.volumes.yml") + : new File("./../../docker/docker-compose.redis.volumes.yml") + )); + if (IS_HYBRID_MODE) { + composeFiles.add(new File("./../../docker/docker-compose.cassandra.volumes.yml")); + } String identifier = Base58.randomString(6).toLowerCase(); String project = identifier + Base58.randomString(6).toLowerCase(); postgresDataVolume = project + "_" + POSTGRES_DATA_VOLUME; + cassandraDataVolume = project + "_" + CASSANDRA_DATA_VOLUME; + redisDataVolume = project + "_" + REDIS_DATA_VOLUME; + redisClusterDataVolume = project + "_" + REDIS_CLUSTER_DATA_VOLUME; tbLogVolume = project + "_" + TB_LOG_VOLUME; tbCoapTransportLogVolume = project + "_" + TB_COAP_TRANSPORT_LOG_VOLUME; tbLwm2mTransportLogVolume = project + "_" + TB_LWM2M_TRANSPORT_LOG_VOLUME; tbHttpTransportLogVolume = project + "_" + TB_HTTP_TRANSPORT_LOG_VOLUME; tbMqttTransportLogVolume = project + "_" + TB_MQTT_TRANSPORT_LOG_VOLUME; tbSnmpTransportLogVolume = project + "_" + TB_SNMP_TRANSPORT_LOG_VOLUME; + tbVcExecutorLogVolume = project + "_" + TB_VC_EXECUTOR_LOG_VOLUME; dockerCompose = new DockerComposeExecutor(composeFiles, project); env = new HashMap<>(); + env.put("JAVA_OPTS", JAVA_OPTS); env.put("POSTGRES_DATA_VOLUME", postgresDataVolume); + if (IS_HYBRID_MODE) { + env.put("CASSANDRA_DATA_VOLUME", cassandraDataVolume); + } env.put("TB_LOG_VOLUME", tbLogVolume); env.put("TB_COAP_TRANSPORT_LOG_VOLUME", tbCoapTransportLogVolume); env.put("TB_LWM2M_TRANSPORT_LOG_VOLUME", tbLwm2mTransportLogVolume); env.put("TB_HTTP_TRANSPORT_LOG_VOLUME", tbHttpTransportLogVolume); env.put("TB_MQTT_TRANSPORT_LOG_VOLUME", tbMqttTransportLogVolume); env.put("TB_SNMP_TRANSPORT_LOG_VOLUME", tbSnmpTransportLogVolume); + env.put("TB_VC_EXECUTOR_LOG_VOLUME", tbVcExecutorLogVolume); + if (IS_REDIS_CLUSTER) { + for (int i = 0; i < 6; i++) { + env.put("REDIS_CLUSTER_DATA_VOLUME_" + i, redisClusterDataVolume + '-' + i); + } + } else { + env.put("REDIS_DATA_VOLUME", redisDataVolume); + } dockerCompose.withEnv(env); } @@ -86,6 +136,11 @@ public class ThingsBoardDbInstaller extends ExternalResource { dockerCompose.withCommand("volume create " + postgresDataVolume); dockerCompose.invokeDocker(); + if (IS_HYBRID_MODE) { + dockerCompose.withCommand("volume create " + cassandraDataVolume); + dockerCompose.invokeDocker(); + } + dockerCompose.withCommand("volume create " + tbLogVolume); dockerCompose.invokeDocker(); @@ -104,7 +159,26 @@ public class ThingsBoardDbInstaller extends ExternalResource { dockerCompose.withCommand("volume create " + tbSnmpTransportLogVolume); dockerCompose.invokeDocker(); - dockerCompose.withCommand("up -d redis postgres"); + dockerCompose.withCommand("volume create " + tbVcExecutorLogVolume); + dockerCompose.invokeDocker(); + + String additionalServices = ""; + if (IS_HYBRID_MODE) { + additionalServices += " cassandra"; + } + if (IS_REDIS_CLUSTER) { + for (int i = 0; i < 6; i++) { + additionalServices = additionalServices + " redis-node-" + i; + dockerCompose.withCommand("volume create " + redisClusterDataVolume + '-' + i); + dockerCompose.invokeDocker(); + } + } else { + additionalServices += " redis"; + dockerCompose.withCommand("volume create " + redisDataVolume); + dockerCompose.invokeDocker(); + } + + dockerCompose.withCommand("up -d postgres" + additionalServices); dockerCompose.invokeCompose(); dockerCompose.withCommand("run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=true tb-core1"); @@ -126,10 +200,14 @@ public class ThingsBoardDbInstaller extends ExternalResource { copyLogs(tbHttpTransportLogVolume, "./target/tb-http-transport-logs/"); copyLogs(tbMqttTransportLogVolume, "./target/tb-mqtt-transport-logs/"); copyLogs(tbSnmpTransportLogVolume, "./target/tb-snmp-transport-logs/"); + copyLogs(tbVcExecutorLogVolume, "./target/tb-vc-executor-logs/"); dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume + " " + tbCoapTransportLogVolume + " " + tbLwm2mTransportLogVolume + " " + tbHttpTransportLogVolume + - " " + tbMqttTransportLogVolume + " " + tbSnmpTransportLogVolume); + " " + tbMqttTransportLogVolume + " " + tbSnmpTransportLogVolume + " " + tbVcExecutorLogVolume + + (IS_REDIS_CLUSTER + ? IntStream.range(0, 6).mapToObj(i -> " " + redisClusterDataVolume + '-' + i).collect(Collectors.joining()) + : redisDataVolume)); dockerCompose.invokeDocker(); } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java index 547ebaf575..d4ee31d9be 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java @@ -36,8 +36,11 @@ public class WsClient extends WebSocketClient { private CountDownLatch firstReply = new CountDownLatch(1); private CountDownLatch latch = new CountDownLatch(1); - WsClient(URI serverUri) { + private final long timeoutMultiplier; + + WsClient(URI serverUri, long timeoutMultiplier) { super(serverUri); + this.timeoutMultiplier = timeoutMultiplier; } @Override @@ -74,8 +77,13 @@ public class WsClient extends WebSocketClient { public WsTelemetryResponse getLastMessage() { try { - latch.await(10, TimeUnit.SECONDS); - return this.message; + boolean result = latch.await(10 * timeoutMultiplier, TimeUnit.SECONDS); + if (result) { + return this.message; + } else { + log.error("Timeout, ws message wasn't received"); + throw new RuntimeException("Timeout, ws message wasn't received"); + } } catch (InterruptedException e) { log.error("Timeout, ws message wasn't received"); } @@ -84,7 +92,11 @@ public class WsClient extends WebSocketClient { void waitForFirstReply() { try { - firstReply.await(10, TimeUnit.SECONDS); + boolean result = firstReply.await(10 * timeoutMultiplier, TimeUnit.SECONDS); + if (!result) { + log.error("Timeout, ws message wasn't received"); + throw new RuntimeException("Timeout, ws message wasn't received"); + } } catch (InterruptedException e) { log.error("Timeout, ws message wasn't received"); throw new RuntimeException(e); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java index a774888af5..f81d03b394 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java @@ -90,7 +90,7 @@ public class HttpClientTest extends AbstractContainerTest { Assert.assertTrue(deviceClientsAttributes.getStatusCode().is2xxSuccessful()); - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); @SuppressWarnings("deprecation") Optional allOptional = restClient.getAttributes(accessToken, null, null); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index 336d75bb45..19a53cb1ce 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -28,9 +28,6 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.junit.*; -import org.junit.rules.TestRule; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; @@ -181,14 +178,14 @@ public class MqttClientTest extends AbstractContainerTest { mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE).get(); // Wait until subscription is processed - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); // Request attributes JsonObject request = new JsonObject(); request.addProperty("clientKeys", "clientAttr"); request.addProperty("sharedKeys", "sharedAttr"); mqttClient.publish("v1/devices/me/attributes/request/" + new Random().nextInt(100), Unpooled.wrappedBuffer(request.toString().getBytes())).get(); - MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); + MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); AttributesResponse attributes = mapper.readValue(Objects.requireNonNull(event).getMessage(), AttributesResponse.class); log.info("Received telemetry: {}", attributes); @@ -212,7 +209,7 @@ public class MqttClientTest extends AbstractContainerTest { mqttClient.on("v1/devices/me/attributes", listener, MqttQoS.AT_LEAST_ONCE).get(); // Wait until subscription is processed - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); String sharedAttributeName = "sharedAttr"; @@ -226,7 +223,7 @@ public class MqttClientTest extends AbstractContainerTest { device.getId()); Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); - MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); + MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); Assert.assertEquals(sharedAttributeValue, mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); @@ -240,7 +237,7 @@ public class MqttClientTest extends AbstractContainerTest { device.getId()); Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful()); - event = listener.getEvents().poll(10, TimeUnit.SECONDS); + event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); Assert.assertEquals(updatedSharedAttributeValue, mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); @@ -258,7 +255,7 @@ public class MqttClientTest extends AbstractContainerTest { mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE).get(); // Wait until subscription is processed - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); // Send an RPC from the server JsonObject serverRpcPayload = new JsonObject(); @@ -277,7 +274,7 @@ public class MqttClientTest extends AbstractContainerTest { }); // Wait for RPC call from the server and send the response - MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS); + MqttEvent requestFromServer = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); Assert.assertEquals("{\"method\":\"getValue\",\"params\":true}", Objects.requireNonNull(requestFromServer).getMessage()); @@ -287,7 +284,7 @@ public class MqttClientTest extends AbstractContainerTest { // Send a response to the server's RPC request mqttClient.publish("v1/devices/me/rpc/response/" + requestId, Unpooled.wrappedBuffer(clientResponse.toString().getBytes())).get(); - ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS); + ResponseEntity serverResponse = future.get(5 * timeoutMultiplier, TimeUnit.SECONDS); service.shutdownNow(); Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful()); Assert.assertEquals(clientResponse.toString(), serverResponse.getBody()); @@ -311,7 +308,7 @@ public class MqttClientTest extends AbstractContainerTest { // Create a new root rule chain RuleChainId ruleChainId = createRootRuleChainForRpcResponse(); - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); // Send the request to the server JsonObject clientRequest = new JsonObject(); clientRequest.addProperty("method", "getResponse"); @@ -320,8 +317,8 @@ public class MqttClientTest extends AbstractContainerTest { mqttClient.publish("v1/devices/me/rpc/request/" + requestId, Unpooled.wrappedBuffer(clientRequest.toString().getBytes())).get(); // Check the response from the server - TimeUnit.SECONDS.sleep(1); - MqttEvent responseFromServer = listener.getEvents().poll(1, TimeUnit.SECONDS); + TimeUnit.SECONDS.sleep(1 * timeoutMultiplier); + MqttEvent responseFromServer = listener.getEvents().poll(1 * timeoutMultiplier, TimeUnit.SECONDS); Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length())); Assert.assertEquals(requestId, responseId); Assert.assertEquals("requestReceived", mapper.readTree(responseFromServer.getMessage()).get("response").asText()); @@ -350,7 +347,7 @@ public class MqttClientTest extends AbstractContainerTest { MqttClient mqttClient = getMqttClient(deviceCredentials, listener); restClient.deleteDevice(device.getId()); - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); Assert.assertFalse(mqttClient.isConnected()); } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java index ec77cbec36..46aa4acd1d 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java @@ -168,7 +168,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, createdDevice.getId()); Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); - var event = listener.getEvents().poll(10, TimeUnit.SECONDS); + var event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); JsonObject requestData = new JsonObject(); requestData.addProperty("id", 1); @@ -178,7 +178,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get(); mqttClient.publish("v1/gateway/attributes/request", Unpooled.wrappedBuffer(requestData.toString().getBytes())).get(); - event = listener.getEvents().poll(10, TimeUnit.SECONDS); + event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); JsonObject responseData = jsonParser.parse(Objects.requireNonNull(event).getMessage()).getAsJsonObject(); Assert.assertTrue(responseData.has("value")); @@ -195,7 +195,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get(); mqttClient.publish("v1/gateway/attributes/request", Unpooled.wrappedBuffer(requestData.toString().getBytes())).get(); - event = listener.getEvents().poll(10, TimeUnit.SECONDS); + event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); responseData = jsonParser.parse(Objects.requireNonNull(event).getMessage()).getAsJsonObject(); Assert.assertTrue(responseData.has("values")); @@ -213,7 +213,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get(); mqttClient.publish("v1/gateway/attributes/request", Unpooled.wrappedBuffer(requestData.toString().getBytes())).get(); - event = listener.getEvents().poll(10, TimeUnit.SECONDS); + event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); responseData = jsonParser.parse(Objects.requireNonNull(event).getMessage()).getAsJsonObject(); Assert.assertTrue(responseData.has("values")); @@ -256,7 +256,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, createdDevice.getId()); Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); - MqttEvent sharedAttributeEvent = listener.getEvents().poll(10, TimeUnit.SECONDS); + MqttEvent sharedAttributeEvent = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); // Catch attribute update event Assert.assertNotNull(sharedAttributeEvent); @@ -266,7 +266,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get(); // Wait until subscription is processed - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); checkAttribute(true, clientAttributeValue); checkAttribute(false, sharedAttributeValue); @@ -276,7 +276,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { public void subscribeToAttributeUpdatesFromServer() throws Exception { mqttClient.on("v1/gateway/attributes", listener, MqttQoS.AT_LEAST_ONCE).get(); // Wait until subscription is processed - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); String sharedAttributeName = "sharedAttr"; // Add a new shared attribute @@ -294,7 +294,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { createdDevice.getId()); Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); - MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); + MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); Assert.assertEquals(sharedAttributeValue, mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()); @@ -313,7 +313,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { createdDevice.getId()); Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful()); - event = listener.getEvents().poll(10, TimeUnit.SECONDS); + event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); Assert.assertEquals(updatedSharedAttributeValue, mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()); } @@ -324,7 +324,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { mqttClient.on(gatewayRpcTopic, listener, MqttQoS.AT_LEAST_ONCE).get(); // Wait until subscription is processed - TimeUnit.SECONDS.sleep(3); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); // Send an RPC from the server JsonObject serverRpcPayload = new JsonObject(); @@ -343,7 +343,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { }); // Wait for RPC call from the server and send the response - MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS); + MqttEvent requestFromServer = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); service.shutdownNow(); Assert.assertNotNull(requestFromServer); @@ -369,7 +369,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { // Send a response to the server's RPC request mqttClient.publish(gatewayRpcTopic, Unpooled.wrappedBuffer(gatewayResponse.toString().getBytes())).get(); - ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS); + ResponseEntity serverResponse = future.get(5 * timeoutMultiplier, TimeUnit.SECONDS); Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful()); Assert.assertEquals(clientResponse.toString(), serverResponse.getBody()); } @@ -396,7 +396,7 @@ public class MqttGatewayClientTest extends AbstractContainerTest { gatewayAttributesRequest.addProperty("key", attributeName); log.info(gatewayAttributesRequest.toString()); mqttClient.publish("v1/gateway/attributes/request", Unpooled.wrappedBuffer(gatewayAttributesRequest.toString().getBytes())).get(); - MqttEvent clientAttributeEvent = listener.getEvents().poll(10, TimeUnit.SECONDS); + MqttEvent clientAttributeEvent = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); Assert.assertNotNull(clientAttributeEvent); JsonObject responseMessage = new JsonParser().parse(Objects.requireNonNull(clientAttributeEvent).getMessage()).getAsJsonObject(); @@ -407,9 +407,17 @@ public class MqttGatewayClientTest extends AbstractContainerTest { } private Device createDeviceThroughGateway(MqttClient mqttClient, Device gatewayDevice) throws Exception { + if (timeoutMultiplier > 1) { + TimeUnit.SECONDS.sleep(30); + } + String deviceName = "mqtt_device"; mqttClient.publish("v1/gateway/connect", Unpooled.wrappedBuffer(createGatewayConnectPayload(deviceName).toString().getBytes()), MqttQoS.AT_LEAST_ONCE).get(); + if (timeoutMultiplier > 1) { + TimeUnit.SECONDS.sleep(30); + } + List relations = restClient.findByFrom(gatewayDevice.getId(), RelationTypeGroup.COMMON); Assert.assertEquals(1, relations.size()); diff --git a/msa/black-box-tests/src/test/resources/docker-compose.rabbitmq-server.yml b/msa/black-box-tests/src/test/resources/docker-compose.rabbitmq-server.yml new file mode 100644 index 0000000000..21aba7061a --- /dev/null +++ b/msa/black-box-tests/src/test/resources/docker-compose.rabbitmq-server.yml @@ -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. +# + +version: '2.2' + +services: + rabbitmq: + restart: always + image: rabbitmq:3 + ports: + - '5672:5672' + environment: + RABBITMQ_DEFAULT_USER: YOUR_USERNAME + RABBITMQ_DEFAULT_PASS: YOUR_PASSWORD + tb-js-executor: + depends_on: + - rabbitmq + tb-core1: + depends_on: + - rabbitmq + tb-core2: + depends_on: + - rabbitmq + tb-rule-engine1: + depends_on: + - rabbitmq + tb-rule-engine2: + depends_on: + - rabbitmq + tb-mqtt-transport1: + depends_on: + - rabbitmq + tb-mqtt-transport2: + depends_on: + - rabbitmq + tb-http-transport1: + depends_on: + - rabbitmq + tb-http-transport2: + depends_on: + - rabbitmq + tb-coap-transport: + depends_on: + - rabbitmq + tb-lwm2m-transport: + depends_on: + - rabbitmq + tb-snmp-transport: + depends_on: + - rabbitmq + tb-vc-executor1: + depends_on: + - rabbitmq + tb-vc-executor2: + depends_on: + - rabbitmq diff --git a/msa/js-executor/api/httpServer.js b/msa/js-executor/api/httpServer.js deleted file mode 100644 index 62bf5807b1..0000000000 --- a/msa/js-executor/api/httpServer.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -const config = require('config'), - logger = require('../config/logger')._logger('httpServer'), - express = require('express'); - -const httpPort = Number(config.get('http_port')); - -const app = express(); - -app.get('/livenessProbe', async (req, res) => { - const date = new Date(); - const message = { now: date.toISOString() }; - res.send(message); -}) - -app.listen(httpPort, () => logger.info(`Started http endpoint on port ${httpPort}. Please, use /livenessProbe !`)) \ No newline at end of file diff --git a/msa/js-executor/api/httpServer.ts b/msa/js-executor/api/httpServer.ts new file mode 100644 index 0000000000..e1c294fdff --- /dev/null +++ b/msa/js-executor/api/httpServer.ts @@ -0,0 +1,65 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import express from 'express'; +import { _logger} from '../config/logger'; +import http from 'http'; +import { Socket } from 'net'; + +export class HttpServer { + + private logger = _logger('httpServer'); + private app = express(); + private server: http.Server | null; + private connections: Socket[] = []; + + constructor(httpPort: number) { + this.app.get('/livenessProbe', async (req, res) => { + const message = { + now: new Date().toISOString() + }; + res.send(message); + }) + + this.server = this.app.listen(httpPort, () => { + this.logger.info('Started HTTP endpoint on port %s. Please, use /livenessProbe !', httpPort); + }).on('error', (error) => { + this.logger.error(error); + }); + + this.server.on('connection', connection => { + this.connections.push(connection); + connection.on('close', () => this.connections = this.connections.filter(curr => curr !== connection)); + }); + } + + async stop() { + if (this.server) { + this.logger.info('Stopping HTTP Server...'); + const _server = this.server; + this.server = null; + this.connections.forEach(curr => curr.end(() => curr.destroy())); + await new Promise( + (resolve, reject) => { + _server.close((err) => { + this.logger.info('HTTP Server stopped.'); + resolve(); + }); + } + ); + } + } +} diff --git a/msa/js-executor/api/jsExecutor.js b/msa/js-executor/api/jsExecutor.js deleted file mode 100644 index 02190406d2..0000000000 --- a/msa/js-executor/api/jsExecutor.js +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -'use strict'; - -const vm = require('vm'); - -function JsExecutor(useSandbox) { - this.useSandbox = useSandbox; -} - -JsExecutor.prototype.compileScript = function(code) { - if (this.useSandbox) { - return createScript(code); - } else { - return createFunction(code); - } -} - -JsExecutor.prototype.executeScript = function(script, args, timeout) { - if (this.useSandbox) { - return invokeScript(script, args, timeout); - } else { - return invokeFunction(script, args); - } -} - -function createScript(code) { - return new Promise((resolve, reject) => { - try { - code = "("+code+")(...args)"; - var script = new vm.Script(code); - resolve(script); - } catch (err) { - reject(err); - } - }); -} - -function invokeScript(script, args, timeout) { - return new Promise((resolve, reject) => { - try { - var sandbox = Object.create(null); - sandbox.args = args; - var result = script.runInNewContext(sandbox, {timeout: timeout}); - resolve(result); - } catch (err) { - reject(err); - } - }); -} - - -function createFunction(code) { - return new Promise((resolve, reject) => { - try { - code = "return ("+code+")(...args)"; - const parsingContext = vm.createContext({}); - const func = vm.compileFunction(code, ['args'], {parsingContext: parsingContext}); - resolve(func); - } catch (err) { - reject(err); - } - }); -} - -function invokeFunction(func, args) { - return new Promise((resolve, reject) => { - try { - var result = func(args); - resolve(result); - } catch (err) { - reject(err); - } - }); -} - -module.exports = JsExecutor; diff --git a/msa/js-executor/api/jsExecutor.models.ts b/msa/js-executor/api/jsExecutor.models.ts new file mode 100644 index 0000000000..db2ced52c4 --- /dev/null +++ b/msa/js-executor/api/jsExecutor.models.ts @@ -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. +/// + + +export interface TbMessage { + scriptIdMSB: string; + scriptIdLSB: string; +} + +export interface RemoteJsRequest { + compileRequest?: JsCompileRequest; + invokeRequest?: JsInvokeRequest; + releaseRequest?: JsReleaseRequest; +} + +export interface JsReleaseRequest extends TbMessage { + functionName: string; +} + +export interface JsInvokeRequest extends TbMessage { + functionName: string; + scriptBody: string; + timeout: number; + args: string[]; +} + +export interface JsCompileRequest extends TbMessage { + functionName: string; + scriptBody: string; +} + + +export interface JsReleaseResponse extends TbMessage { + success: boolean; +} + +export interface JsCompileResponse extends TbMessage { + success: boolean; + errorCode?: number; + errorDetails?: string; +} + +export interface JsInvokeResponse { + success: boolean; + result: string; + errorCode?: number; + errorDetails?: string; +} + +export interface RemoteJsResponse { + requestIdMSB: string; + requestIdLSB: string; + compileResponse?: JsCompileResponse; + invokeResponse?: JsInvokeResponse; + releaseResponse?: JsReleaseResponse; +} diff --git a/msa/js-executor/api/jsExecutor.ts b/msa/js-executor/api/jsExecutor.ts new file mode 100644 index 0000000000..f22f14281b --- /dev/null +++ b/msa/js-executor/api/jsExecutor.ts @@ -0,0 +1,93 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import vm, { Script } from 'vm'; + +export type TbScript = Script | Function; + +export class JsExecutor { + useSandbox: boolean; + + constructor(useSandbox: boolean) { + this.useSandbox = useSandbox; + } + + compileScript(code: string): Promise { + if (this.useSandbox) { + return this.createScript(code); + } else { + return this.createFunction(code); + } + } + + executeScript(script: TbScript, args: string[], timeout?: number): Promise { + if (this.useSandbox) { + return this.invokeScript(script as Script, args, timeout); + } else { + return this.invokeFunction(script as Function, args); + } + } + + private createScript(code: string): Promise