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..6d2c95ee84 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..69df2afdc9 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,40 @@
-- 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_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_rule_chain_external_id ON rule_chain(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_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_entity_view_external_id ON entity_view(tenant_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 +69,4 @@ 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
);
+
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..7e689e58fa 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;
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 1415ff4ece..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,9 +15,11 @@
*/
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.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;
@@ -41,6 +43,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+@Slf4j
@Component
public class RateLimitProcessingFilter extends GenericFilterBean {
@@ -58,7 +61,13 @@ public class RateLimitProcessingFilter extends GenericFilterBean {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
SecurityUser user = getCurrentUser();
if (user != null && !user.isSystemAdmin()) {
- var profileConfiguration = tenantProfileCache.get(user.getTenantId()).getDefaultProfileConfiguration();
+ 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;
}
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..76c631bddb 100644
--- a/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
+++ b/application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
@@ -181,13 +181,18 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
@Autowired
private OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver;
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web.ignoring().antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**");
+ }
+
@Override
protected void configure(HttpSecurity http) throws Exception {
- http.authorizeHttpRequests((authorizeHttpRequests) ->
- authorizeHttpRequests
- .antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")
- .permitAll()
- );
+// http.authorizeHttpRequests((authorizeHttpRequests) ->
+// authorizeHttpRequests
+// .antMatchers("/*.js","/*.css","/*.ico","/assets/**","/static/**")
+// .permitAll()
+// );
http.headers().cacheControl().and().frameOptions().disable()
.and()
.cors()
diff --git a/application/src/main/java/org/thingsboard/server/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 f53eb28e20..2c171e8ca6 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -222,11 +222,7 @@ public class AlarmController extends BaseController {
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try {
- if (getCurrentUser().isCustomerUser()) {
- return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
- } else {
- return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
- }
+ return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
} catch (Exception e) {
throw handleException(e);
}
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 77df4100dc..13ef646eaa 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.service.sync.ie.importing.csv.BulkImportRequest;
+import org.thingsboard.server.service.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;
@@ -143,7 +143,7 @@ public class AssetController extends BaseController {
@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);
}
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 0f8c40956e..a26de329b3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -17,6 +17,10 @@ package org.thingsboard.server.controller;
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;
@@ -28,6 +32,7 @@ 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.DeferredResult;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
@@ -134,6 +139,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;
@@ -277,6 +283,9 @@ public abstract class BaseController {
@Autowired
protected QueueService queueService;
+ @Autowired
+ protected EntitiesVersionControlService vcService;
+
@Value("${server.log_controller_error_stack_trace}")
@Getter
private boolean logControllerErrorStackTrace;
@@ -883,15 +892,15 @@ public abstract class BaseController {
}
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);
+ private void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) {
+ tbClusterService.sendNotificationMsgToEdge(tenantId, edgeId, entityId, body, type, action);
}
protected List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) {
@@ -926,4 +935,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..6a616671eb 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.";
@@ -135,6 +136,9 @@ 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 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 6dba5584df..2510974991 100644
--- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
@@ -142,7 +142,7 @@ public class CustomerController extends BaseController {
@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());
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 05ef42017f..0530f59c97 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
@@ -180,7 +180,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());
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 86b4e3ebef..183b5ea048 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.service.sync.ie.importing.csv.BulkImportRequest;
+import org.thingsboard.server.service.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;
@@ -165,7 +165,7 @@ public class DeviceController extends BaseController {
@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) {
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 72d9b1c654..08568a28ce 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
@@ -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);
}
@@ -199,7 +199,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());
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 9cecf86885..b3c1792b6a 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.service.sync.ie.importing.csv.BulkImportRequest;
+import org.thingsboard.server.service.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;
@@ -557,7 +558,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..dfefce4b12
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/controller/EntitiesVersionControlController.java
@@ -0,0 +1,508 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.READ);
+ 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(@PathVariable EntityType entityType,
+ @PathVariable UUID externalEntityUuid,
+ @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(@PathVariable EntityType entityType,
+ @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(@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}", params = {"branch"})
+ public DeferredResult> listEntitiesAtVersion(@PathVariable EntityType entityType,
+ @ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
+ @PathVariable String versionId,
+ @RequestParam String branch) throws Exception {
+ accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
+ return wrapFuture(versionControlService.listEntitiesAtVersion(getTenantId(), branch, 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}", params = {"branch"})
+ public DeferredResult> listAllEntitiesAtVersion(@ApiParam(value = VERSION_ID_PARAM_DESCRIPTION, required = true)
+ @PathVariable String versionId,
+ @RequestParam String branch) throws Exception {
+ accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ);
+ return wrapFuture(versionControlService.listAllEntitiesAtVersion(getTenantId(), branch, 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,
+ @PathVariable EntityType entityType,
+ @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 = {"branch", "versionId"})
+ public DeferredResult compareEntityDataToVersion(@PathVariable EntityType entityType,
+ @PathVariable UUID internalEntityUuid,
+ @RequestParam String branch,
+ @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(), branch, 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.READ);
+ 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 50b82847ac..efc4237b3f 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
@@ -37,6 +37,7 @@ 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.entity.relation.TbEntityRelationService;
+import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.permission.Operation;
import java.util.List;
@@ -80,8 +81,8 @@ 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);
}
@@ -107,8 +108,9 @@ 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());
@@ -340,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/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 2db9386373..38e7f5bb7d 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -233,7 +233,7 @@ public class RuleChainController extends BaseController {
@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());
@@ -247,7 +247,7 @@ public class RuleChainController extends BaseController {
@ResponseBody
public RuleChain saveRuleChain(
@ApiParam(value = "A JSON value representing the request.")
- @RequestBody DefaultRuleChainCreateRequest request) throws ThingsboardException, IOException {
+ @RequestBody DefaultRuleChainCreateRequest request) throws Exception {
checkNotNull(request);
checkParameter(request.getName(), "name");
return tbRuleChainService.saveDefaultByName(getTenantId(), request, getCurrentUser());
@@ -277,7 +277,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();
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 bc0fa376b1..4342a647c5 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java
@@ -146,7 +146,7 @@ public class TbResourceController extends BaseController {
@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/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
index 320fdc73bc..ef3c141131 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
@@ -131,7 +131,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/service/action/EntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/EntityActionService.java
index 2f9a1b166e..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,7 +42,6 @@ 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;
@@ -211,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();
@@ -222,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;
@@ -266,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/apiusage/RateLimitService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.java
new file mode 100644
index 0000000000..d3d4244ca1
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/apiusage/RateLimitService.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.apiusage;
+
+import org.thingsboard.server.common.data.id.TenantId;
+
+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..5eaeec0b73 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.service.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/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
index 25a3a2d156..c04dd5ce15 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.service.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;
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..dc2b9f4089 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:
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..404e5af5fd 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.service.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..6863fbc1c0 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,7 @@ 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.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserService;
@@ -41,6 +42,7 @@ 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.RelationEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.TelemetryEdgeProcessor;
@@ -93,6 +95,9 @@ public class EdgeContextComponent {
@Autowired
private EdgeRequestsService edgeRequestsService;
+ @Autowired
+ private OtaPackageService otaPackageService;
+
@Autowired
private AlarmEdgeProcessor alarmProcessor;
@@ -138,6 +143,9 @@ public class EdgeContextComponent {
@Autowired
private AdminSettingsEdgeProcessor adminSettingsProcessor;
+ @Autowired
+ private OtaPackageEdgeProcessor otaPackageEdgeProcessor;
+
@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..1d30d05fea 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,8 @@ 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);
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..c880916e35 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,7 @@ 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.RuleChainsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.SystemWidgetsBundlesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.TenantAdminUsersEdgeEventFetcher;
@@ -53,6 +54,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..7943ec0595 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;
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/RuleChainMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/RuleChainMsgConstructor.java
index 0fc62cfe41..db67ecf822 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
@@ -21,7 +21,9 @@ 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.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.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain;
@@ -49,9 +51,8 @@ import java.util.stream.Collectors;
@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";
+ private static final String RULE_CHAIN_INPUT_NODE = TbRuleChainInputNode.class.getName();
+ private static final String TB_RULE_CHAIN_OUTPUT_NODE = TbRuleChainOutputNode.class.getName();
public RuleChainUpdateMsg constructRuleChainUpdatedMsg(RuleChainId edgeRootRuleChainId, UpdateMsgType msgType, RuleChain ruleChain) {
RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder()
@@ -210,13 +211,11 @@ public class RuleChainMsgConstructor {
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);
+ 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;
@@ -280,7 +279,7 @@ public class RuleChainMsgConstructor {
.setTargetRuleChainIdMSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getMostSignificantBits())
.setTargetRuleChainIdLSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getLeastSignificantBits())
.setType(ruleChainConnectionInfo.getType())
- .setAdditionalInfo(objectMapper.writeValueAsString(additionalInfo))
+ .setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.writeValueAsString(additionalInfo))
.build();
}
@@ -291,8 +290,8 @@ public class RuleChainMsgConstructor {
.setType(node.getType())
.setName(node.getName())
.setDebugMode(node.isDebugMode())
- .setConfiguration(objectMapper.writeValueAsString(node.getConfiguration()))
- .setAdditionalInfo(objectMapper.writeValueAsString(node.getAdditionalInfo()))
+ .setConfiguration(JacksonUtil.OBJECT_MAPPER.writeValueAsString(node.getConfiguration()))
+ .setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.writeValueAsString(node.getAdditionalInfo()))
.build();
}
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/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..8f37d743b5 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,15 @@ 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.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
+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 +65,7 @@ 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.RelationMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.RuleChainMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor;
@@ -137,6 +142,16 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected WidgetTypeService widgetTypeService;
+ @Autowired
+ protected OtaPackageService otaPackageService;
+
+ @Autowired
+ protected PartitionService partitionService;
+
+ @Autowired
+ @Lazy
+ protected TbQueueProducerProvider producerProvider;
+
@Autowired
protected DataValidator deviceValidator;
@@ -182,6 +197,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected AdminSettingsMsgConstructor adminSettingsMsgConstructor;
+ @Autowired
+ protected OtaPackageMsgConstructor otaPackageMsgConstructor;
+
@Autowired
protected DbCallbackExecutorService dbCallbackExecutorService;
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/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java
index 769d988507..f30111e1ef 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()));
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..571e6e5aac 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,6 +72,7 @@ import org.thingsboard.server.gen.edge.v1.RelationRequestMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.WidgetBundleTypesRequestMsg;
+import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import java.util.ArrayList;
@@ -100,8 +101,9 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
@Autowired
private DeviceService deviceService;
+ @Lazy
@Autowired
- private EntityViewService entityViewService;
+ private TbEntityViewService entityViewService;
@Autowired
private DeviceProfileService deviceProfileService;
@@ -378,7 +380,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 +415,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 55f55d54eb..22bf93cd2c 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,6 +23,7 @@ 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.User;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@@ -40,11 +41,13 @@ import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
+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
@@ -61,7 +64,7 @@ public abstract class AbstractTbEntityService {
@Autowired
protected DbCallbackExecutorService dbExecutor;
- @Autowired
+ @Autowired(required = false)
protected TbNotificationEntityService notificationEntityService;
@Autowired(required = false)
protected EdgeService edgeService;
@@ -71,6 +74,8 @@ public abstract class AbstractTbEntityService {
protected CustomerService customerService;
@Autowired
protected TbClusterService tbClusterService;
+ @Autowired(required = false)
+ private EntitiesVersionControlService vcService;
protected ListenableFuture removeAlarmsByEntityId(TenantId tenantId, EntityId entityId) {
ListenableFuture> alarmsFuture =
@@ -127,4 +132,13 @@ public abstract class AbstractTbEntityService {
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!"));
+ }
+ }
}
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 cbe8e2af11..5f6ca9ce4b 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
@@ -262,7 +262,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, additionalInfo);
try {
if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && !relation.getTo().getEntityType().equals(EntityType.EDGE)) {
- sendNotificationMsgToEdgeService(tenantId, null, null, JacksonUtil.toString(relation),
+ sendNotificationMsgToEdge(tenantId, null, null, JacksonUtil.toString(relation),
EdgeEventType.RELATION, edgeTypeByActionType(actionType));
}
} catch (Exception e) {
@@ -271,18 +271,18 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
}
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, JacksonUtil.toString(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) {
@@ -290,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) {
@@ -302,18 +301,18 @@ 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) {
@@ -348,14 +347,14 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS
return EdgeEventActionType.RELATION_ADD_OR_UPDATE;
case RELATION_DELETED:
return EdgeEventActionType.RELATION_DELETED;
- case ASSIGNED_TO_EDGE:
- return EdgeEventActionType.ASSIGNED_TO_EDGE;
- case UNASSIGNED_FROM_EDGE:
- return EdgeEventActionType.UNASSIGNED_FROM_EDGE;
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:
+ return EdgeEventActionType.UNASSIGNED_FROM_EDGE;
default:
return null;
}
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 49b94f4921..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
@@ -16,15 +16,14 @@
package org.thingsboard.server.service.entitiy;
import org.thingsboard.server.common.data.User;
-import org.thingsboard.server.common.data.exception.ThingsboardException;
public interface SimpleTbEntityService {
- default T save(T entity) throws ThingsboardException {
+ default T save(T entity) throws Exception {
return save(entity, null);
}
- T save(T entity, User 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/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java
index 8b56ed0bc2..d73e384658 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
@@ -41,11 +41,12 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb
private final AssetService assetService;
@Override
- public Asset save(Asset asset, User 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));
+ autoCommit(user, savedAsset.getId());
notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), savedAsset,
asset.getCustomerId(), actionType, user);
return savedAsset;
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 7c4f337abc..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
@@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId;
public interface TbAssetService {
- Asset save(Asset asset, User user) throws ThingsboardException;
+ Asset save(Asset asset, User user) throws Exception;
ListenableFuture delete(Asset asset, User user);
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 2e7ab54a23..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
@@ -21,7 +21,6 @@ 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;
@@ -35,14 +34,13 @@ import java.util.List;
public class DefaultTbCustomerService extends AbstractTbEntityService implements TbCustomerService {
@Override
- public Customer save(Customer customer, User 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.logEntityAction(tenantId, emptyId(EntityType.CUSTOMER), customer, actionType, user, 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 f815be7f36..dc035798e3 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
@@ -45,11 +45,12 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement
private final DashboardService dashboardService;
@Override
- public Dashboard save(Dashboard dashboard, User 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;
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 43c22b53a3..86d187c1f6 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
@@ -58,11 +58,12 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
private final TenantService tenantService;
@Override
- public Device save(Device device, Device oldDevice, String accessToken, User 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);
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 fd51caacf5..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
@@ -31,7 +31,7 @@ import org.thingsboard.server.dao.device.claim.ReclaimResult;
public interface TbDeviceService {
- Device save(Device device, Device oldDevice, String accessToken, User user) throws ThingsboardException;
+ Device save(Device device, Device oldDevice, String accessToken, User user) throws Exception;
Device saveDeviceWithCredentials(Device device, DeviceCredentials deviceCredentials, User user) throws ThingsboardException;
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java
index 0d6b870e21..650b35db0d 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/profile/DefaultTbDeviceProfileService.java
@@ -43,7 +43,7 @@ public class DefaultTbDeviceProfileService extends AbstractTbEntityService imple
private final OtaPackageStateService otaPackageStateService;
@Override
- public DeviceProfile save(DeviceProfile deviceProfile, User 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 {
@@ -60,7 +60,7 @@ 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);
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
index a49b17ee51..08f241d112 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
@@ -23,6 +23,7 @@ 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;
@@ -40,10 +41,11 @@ 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.telemetry.TelemetrySubscriptionService;
@@ -52,13 +54,14 @@ 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 {
@@ -68,23 +71,42 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
private final TelemetrySubscriptionService tsSubService;
private final TimeseriesService tsService;
+ final Map>> localCache = new ConcurrentHashMap<>();
+
@Override
public EntityView save(EntityView entityView, EntityView existingEntityView, User user) throws ThingsboardException {
ActionType actionType = entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = entityView.getTenantId();
+ try {
+ EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
+ this.updateEntityViewAttributes(tenantId, savedEntityView, existingEntityView, user);
+ 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.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 {
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));
+
+ 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 = existingEntityView.getKeys() != null && existingEntityView.getKeys().getTimeseries() != null ?
- existingEntityView.getKeys().getTimeseries() : Collections.emptyList();
- futures.add(deleteLatestFromEntityView(existingEntityView, tsKeys, user));
+ List tsKeys = oldEntityView.getKeys() != null && oldEntityView.getKeys().getTimeseries() != null ?
+ oldEntityView.getKeys().getTimeseries() : Collections.emptyList();
+ futures.add(deleteLatestFromEntityView(oldEntityView, 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));
@@ -100,14 +122,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
throw new RuntimeException("Failed to copy attributes to entity view", e);
}
}
-
- notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedEntityView.getId(), savedEntityView,
- null, actionType, user);
-
- return savedEntityView;
} catch (Exception e) {
- notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW), entityView,
- actionType, user, e);
throw e;
}
}
@@ -121,6 +136,9 @@ 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.logEntityAction(tenantId, emptyId(EntityType.ENTITY_VIEW),
ActionType.DELETED, user, e, entityViewId.toString());
@@ -208,6 +226,51 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen
}
}
+ @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, User user) throws ThingsboardException {
EntityViewId entityId = entityView.getId();
if (keys != null && !keys.isEmpty()) {
@@ -223,8 +286,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
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
index 7012e39aa6..28ead13853 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
@@ -15,19 +15,26 @@
*/
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.common.msg.plugin.ComponentLifecycleListener;
-public interface TbEntityViewService {
+import java.util.List;
+
+public interface TbEntityViewService extends ComponentLifecycleListener {
EntityView save(EntityView entityView, EntityView existingEntityView, User 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;
@@ -40,4 +47,6 @@ public interface TbEntityViewService {
EntityView unassignEntityViewFromEdge(TenantId tenantId, CustomerId customerId, EntityView entityView, Edge edge, User user) throws ThingsboardException;
EntityView unassignEntityViewFromCustomer(TenantId tenantId, EntityViewId entityViewId, Customer customer, User user) throws ThingsboardException;
+
+ ListenableFuture> findEntityViewsByTenantIdAndEntityIdAsync(TenantId tenantId, EntityId entityId);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java
index 68b115e70a..00cf70db0e 100644
--- a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java
+++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java
@@ -48,9 +48,12 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen
ActionType actionType = saveOtaPackageInfoRequest.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = saveOtaPackageInfoRequest.getTenantId();
try {
- OtaPackageInfo savedOtaPackageInfo = checkNotNull(otaPackageService.saveOtaPackageInfo(
- new OtaPackageInfo(saveOtaPackageInfoRequest), saveOtaPackageInfoRequest.isUsesUrl()));
- notificationEntityService.logEntityAction(tenantId, savedOtaPackageInfo.getId(), savedOtaPackageInfo, actionType, user);
+ OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(saveOtaPackageInfoRequest), saveOtaPackageInfoRequest.isUsesUrl());
+
+ boolean sendToEdge = savedOtaPackageInfo.hasUrl() || savedOtaPackageInfo.isHasData();
+ notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackageInfo.getId(),
+ savedOtaPackageInfo, user, actionType, sendToEdge, null);
+
return savedOtaPackageInfo;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), saveOtaPackageInfoRequest,
@@ -84,7 +87,8 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen
otaPackage.setData(ByteBuffer.wrap(data));
otaPackage.setDataSize((long) data.length);
OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage);
- notificationEntityService.logEntityAction(tenantId, savedOtaPackage.getId(), savedOtaPackage, ActionType.UPDATED, user);
+ notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackage.getId(),
+ savedOtaPackage, user, ActionType.UPDATED, true, null);
return savedOtaPackage;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.OTA_PACKAGE), ActionType.UPDATED,
@@ -99,8 +103,8 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen
OtaPackageId otaPackageId = otaPackageInfo.getId();
try {
otaPackageService.deleteOtaPackage(tenantId, otaPackageId);
- notificationEntityService.logEntityAction(tenantId, otaPackageId, otaPackageInfo, ActionType.DELETED, user,
- otaPackageInfo.getId().toString());
+ 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());
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 50dc591a52..d6dbb97269 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,7 +19,6 @@ 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;
@@ -29,8 +28,10 @@ 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
@@ -42,6 +43,7 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
private final InstallScripts installScripts;
private final TbQueueService tbQueueService;
private final TenantProfileService tenantProfileService;
+ private final EntitiesVersionControlService versionControlService;
@Override
public Tenant save(Tenant tenant) throws Exception {
@@ -64,10 +66,15 @@ public class DefaultTbTenantService extends AbstractTbEntityService implements T
}
@Override
- public void delete(Tenant tenant) throws ThingsboardException {
- TenantId tenantId = tenant.getId();
- tenantService.deleteTenant(tenantId);
- tenantProfileCache.evict(tenantId);
- notificationEntityService.notifyDeleteTenant(tenant);
+ public void delete(Tenant tenant) throws Exception {
+ try {
+ TenantId tenantId = tenant.getId();
+ tenantService.deleteTenant(tenantId);
+ tenantProfileCache.evict(tenantId);
+ notificationEntityService.notifyDeleteTenant(tenant);
+ versionControlService.deleteVersionControlSettings(tenantId).get(1, TimeUnit.MINUTES);
+ } catch (Exception e) {
+ throw e;
+ }
}
}
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 20e910f7b0..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 Exception;
- void delete(Tenant tenant) throws ThingsboardException;
+ void delete(Tenant tenant) throws Exception;
}
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..14e49e056f 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;
@@ -265,6 +267,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
@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 +276,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..e428d55d91 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;
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 c6576097ee..9bfa86b3ba 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
@@ -22,6 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
@@ -117,6 +118,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired
private TenantProfileService tenantProfileService;
+ @Lazy
@Autowired
private QueueService queueService;
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..9e60f69620 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);
@@ -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 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 getRuleChainOutputLabels(TenantId tenantId, RuleChainId ruleChainId) {
RuleChainMetaData metaData = ruleChainService.loadRuleChainMetaData(tenantId, ruleChainId);
@@ -167,11 +171,13 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
}
@Override
- public RuleChain save(RuleChain ruleChain, User 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;
- RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
try {
+ RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
+ vcService.autoCommit(user, savedRuleChain.getId());
+
if (RuleChainType.CORE.equals(savedRuleChain.getType())) {
tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(),
actionType.equals(ActionType.ADDED) ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
@@ -220,9 +226,10 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
}
@Override
- public RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, User user) throws IOException {
+ public RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, User user) throws Exception {
try {
RuleChain savedRuleChain = installScripts.createDefaultRuleChain(tenantId, request.getName());
+ vcService.autoCommit(user, savedRuleChain.getId());
tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedRuleChain.getId(), ComponentLifecycleEvent.CREATED);
notificationEntityService.logEntityAction(tenantId, savedRuleChain.getId(), savedRuleChain, ActionType.ADDED, user);
return savedRuleChain;
@@ -266,7 +273,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
@Override
public RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChain ruleChain, RuleChainMetaData ruleChainMetaData,
- boolean updateRelated, User user) throws ThingsboardException {
+ boolean updateRelated, User user) throws Exception {
RuleChainId ruleChainId = ruleChain.getId();
RuleChainId ruleChainMetaDataId = ruleChainMetaData.getRuleChainId();
try {
@@ -280,6 +287,15 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement
updatedRuleChains = Collections.emptyList();
}
+ if (updatedRuleChains.isEmpty()) {
+ vcService.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()));
+ vcService.autoCommit(user, EntityType.RULE_CHAIN, uuids);
+ }
+
RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaDataId));
if (RuleChainType.CORE.equals(ruleChain.getType())) {
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 79817667f3..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
@@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage;
import org.thingsboard.server.common.data.rule.RuleChainUpdateResult;
import org.thingsboard.server.service.entitiy.SimpleTbEntityService;
-import java.io.IOException;
import java.util.List;
import java.util.Set;
@@ -39,12 +38,12 @@ public interface TbRuleChainService extends SimpleTbEntityService {
List updateRelatedRuleChains(TenantId tenantId, RuleChainId ruleChainId, RuleChainUpdateResult result);
- RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, User user) throws IOException;
+ RuleChain saveDefaultByName(TenantId tenantId, DefaultRuleChainCreateRequest request, User user) throws Exception;
RuleChain setRootRuleChain(TenantId tenantId, RuleChain ruleChain, User user) throws ThingsboardException;
RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChain ruleChain, RuleChainMetaData ruleChainMetaData,
- boolean updateRelated, User user) throws ThingsboardException;
+ boolean updateRelated, User user) throws Exception;
RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChain ruleChain, Edge edge, User user) throws ThingsboardException;
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 RedisSerializer<>() {
@Override
public byte[] serialize(TransportProtos.DeviceSessionsCacheEntry deviceSessionsCacheEntry) throws SerializationException {
return deviceSessionsCacheEntry.toByteArray();
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..b2abe2a581
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/DefaultEntitiesExportImportService.java
@@ -0,0 +1,166 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.action.EntityActionService;
+import org.thingsboard.server.service.apiusage.RateLimitService;
+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.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 EntityActionService entityActionService;
+ private final RelationService relationService;
+ private final RateLimitService rateLimitService;
+
+ 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(importResult.getSaveReferencesCallback());
+ ctx.addEventCallback(importResult.getSendEventsCallback());
+ return importResult;
+ }
+
+ @Override
+ public void saveReferencesAndRelations(EntitiesImportCtx ctx) throws ThingsboardException {
+ for (ThrowingRunnable saveReferencesCallback : ctx.getReferenceCallbacks()) {
+ saveReferencesCallback.run();
+ }
+
+ relationService.saveRelations(ctx.getTenantId(), new ArrayList<>(ctx.getRelations()));
+
+ for (EntityRelation relation : ctx.getRelations()) {
+ entityActionService.logEntityAction(ctx.getUser(), relation.getFrom(), null, null,
+ ActionType.RELATION_ADD_OR_UPDATE, null, relation);
+ entityActionService.logEntityAction(ctx.getUser(), relation.getTo(), null, null,
+ ActionType.RELATION_ADD_OR_UPDATE, null, 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 extends EntityId> 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..169b7f273c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/exporting/impl/BaseEntityExportService.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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..7e236370df
--- /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.RelationService;
+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 RelationService relationService;
+ @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 = relationService.findByTo(ctx.getTenantId(), entity.getId(), RelationTypeGroup.COMMON);
+ relations.addAll(inboundRelations);
+
+ List outboundRelations = relationService.findByFrom(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 97%
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..b92e100401 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;
@@ -44,7 +44,6 @@ 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 +66,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 +163,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 +247,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 +278,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/BulkImportColumnType.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportColumnType.java
similarity index 97%
rename from application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java
rename to application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportColumnType.java
index 96075eb4c8..24b566e631 100644
--- a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java
+++ b/application/src/main/java/org/thingsboard/server/service/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.service.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/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportRequest.java
similarity index 94%
rename from application/src/main/java/org/thingsboard/server/service/importing/BulkImportRequest.java
rename to application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportRequest.java
index 9f8195ca45..e8eac6a9ed 100644
--- a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportRequest.java
+++ b/application/src/main/java/org/thingsboard/server/service/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.service.sync.ie.importing.csv;
import lombok.Data;
diff --git a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportResult.java b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportResult.java
similarity index 94%
rename from application/src/main/java/org/thingsboard/server/service/importing/BulkImportResult.java
rename to application/src/main/java/org/thingsboard/server/service/sync/ie/importing/csv/BulkImportResult.java
index 651aedeb0b..0626c8e690 100644
--- a/application/src/main/java/org/thingsboard/server/service/importing/BulkImportResult.java
+++ b/application/src/main/java/org/thingsboard/server/service/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.service.sync.ie.importing.csv;
import lombok.Data;
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..ac9a38aa36
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/AssetImportService.java
@@ -0,0 +1,81 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.service.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.asset.Asset;
+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.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 void onEntitySaved(User user, Asset savedAsset, Asset oldAsset) throws ThingsboardException {
+ super.onEntitySaved(user, savedAsset, oldAsset);
+ if (oldAsset != null) {
+ entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedAsset.getId(), EdgeEventActionType.UPDATED);
+ }
+ }
+
+ @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..d46305a41d
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/BaseEntityImportService.java
@@ -0,0 +1,396 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.HasCustomerId;
+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.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 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(relationService.findByTo(tenantId, entity.getId(), RelationTypeGroup.COMMON));
+ existingRelations.addAll(relationService.findByFrom(tenantId, entity.getId(), RelationTypeGroup.COMMON));
+
+ for (EntityRelation existingRelation : existingRelations) {
+ EntityRelation relation = relationsMap.get(existingRelation);
+ if (relation == null) {
+ importResult.setUpdatedRelatedEntities(true);
+ relationService.deleteRelation(tenantId, existingRelation);
+ importResult.addSendEventsCallback(() -> {
+ entityActionService.logEntityAction(ctx.getUser(), existingRelation.getFrom(), null, null,
+ ActionType.RELATION_DELETED, null, existingRelation);
+ entityActionService.logEntityAction(ctx.getUser(), existingRelation.getTo(), null, null,
+ ActionType.RELATION_DELETED, null, 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 {
+ entityActionService.logEntityAction(user, savedEntity.getId(), savedEntity,
+ savedEntity instanceof HasCustomerId ? ((HasCustomerId) savedEntity).getCustomerId() : user.getCustomerId(),
+ oldEntity == null ? ActionType.ADDED : ActionType.UPDATED, null);
+ }
+
+
+ @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;
+
+ 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..be1ef9ed8c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/CustomerImportService.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.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.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.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
+ protected void onEntitySaved(User user, Customer savedCustomer, Customer oldCustomer) throws ThingsboardException {
+ super.onEntitySaved(user, savedCustomer, oldCustomer);
+ if (oldCustomer != null) {
+ entityActionService.sendEntityNotificationMsgToEdge(user.getTenantId(), savedCustomer.getId(), EdgeEventActionType.UPDATED);
+ }
+ }
+
+ @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..9e6083c61c
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/DashboardImportService.java
@@ -0,0 +1,133 @@
+/**
+ * Copyright © 2016-2022 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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