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 f0e93f5662..95dcc7c64c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.controller; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -26,7 +27,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.DashboardInfo; @@ -39,12 +39,12 @@ import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; @@ -595,6 +595,12 @@ public abstract class BaseController { case ALARM_CLEAR: msgType = DataConstants.ALARM_CLEAR; break; + case SWAPPED_FROM_TENANT: + msgType = DataConstants.ENTITY_SWAPPED_FROM; + break; + case SWAPPED_TO_TENANT: + msgType = DataConstants.ENTITY_SWAPPED_TO; + break; } if (!StringUtils.isEmpty(msgType)) { try { @@ -614,6 +620,16 @@ public abstract class BaseController { String strCustomerName = extractParameter(String.class, 2, additionalInfo); metaData.putValue("unassignedCustomerId", strCustomerId); metaData.putValue("unassignedCustomerName", strCustomerName); + } else if (actionType == ActionType.SWAPPED_FROM_TENANT) { + String strTenantId = extractParameter(String.class, 0, additionalInfo); + String strTenantName = extractParameter(String.class, 1, additionalInfo); + metaData.putValue("swappedFromTenantId", strTenantId); + metaData.putValue("swappedFromTenantName", strTenantName); + } else if (actionType == ActionType.SWAPPED_TO_TENANT) { + String strTenantId = extractParameter(String.class, 0, additionalInfo); + String strTenantName = extractParameter(String.class, 1, additionalInfo); + metaData.putValue("swappedToTenantId", strTenantId); + metaData.putValue("swappedToTenantName", strTenantName); } ObjectNode entityNode; if (entity != null) { @@ -677,5 +693,13 @@ public abstract class BaseController { return result; } + protected String entityToStr(E entity) { + try { + return json.writeValueAsString(json.valueToTree(entity)); + } catch (JsonProcessingException e) { + log.warn("[{}] Failed to convert entity to string!", entity, e); + } + return null; + } } 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 f6f327d7d9..61505ddc90 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -39,8 +39,10 @@ import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.device.DeviceSearchQuery; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; @@ -48,6 +50,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.device.claim.ClaimResponse; import org.thingsboard.server.dao.device.claim.ClaimResult; import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -70,6 +75,7 @@ public class DeviceController extends BaseController { private static final String DEVICE_ID = "deviceId"; private static final String DEVICE_NAME = "deviceName"; + private static final String TENANT_ID = "tenantId"; @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET) @@ -481,4 +487,54 @@ public class DeviceController extends BaseController { } return DataConstants.DEFAULT_SECRET_KEY; } + + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST) + @ResponseBody + public Device swapDevice(@PathVariable(TENANT_ID) String strTenantId, + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { + checkParameter(TENANT_ID, strTenantId); + checkParameter(DEVICE_ID, strDeviceId); + try { + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); + Device device = checkDeviceId(deviceId, Operation.WRITE); + + TenantId newTenantId = new TenantId(toUUID(strTenantId)); + Tenant newTenant = tenantService.findTenantById(newTenantId); + if (newTenant == null) { + throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + + Device swappedDevice = deviceService.swapDevice(newTenantId, device); + + logEntityAction(getCurrentUser(), deviceId, swappedDevice, + swappedDevice.getCustomerId(), + ActionType.SWAPPED_TO_TENANT, null, strTenantId, newTenant.getName()); + + Tenant currentTenant = tenantService.findTenantById(getTenantId()); + pushSwappedFromNotification(currentTenant, newTenantId, swappedDevice); + + return swappedDevice; + } catch (Exception e) { + logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null, + null, + ActionType.SWAPPED_TO_TENANT, e, strTenantId); + throw handleException(e); + } + } + + private void pushSwappedFromNotification(Tenant currentTenant, TenantId newTenantId, Device swappedDevice) { + String data = entityToStr(swappedDevice); + if (data != null) { + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_SWAPPED_FROM, swappedDevice.getId(), getMetaDataForSwappedFrom(currentTenant), TbMsgDataType.JSON, data); + tbClusterService.pushMsgToRuleEngine(newTenantId, swappedDevice.getId(), tbMsg, null); + } + } + + private TbMsgMetaData getMetaDataForSwappedFrom(Tenant tenant) { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue("swappedFromTenantId", tenant.getId().getId().toString()); + metaData.putValue("swappedFromTenantName", tenant.getName()); + return metaData; + } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java index 1103f25c2e..f54cda6681 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java @@ -16,14 +16,12 @@ package org.thingsboard.server.dao.audit; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; @@ -50,4 +48,5 @@ public interface AuditLogService { ActionType actionType, Exception e, Object... additionalInfo); + void removeAuditLogs(TenantId tenantId, EntityId entityId); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index a6e06616b2..20516be837 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.page.TextPageLink; import java.util.List; public interface DeviceService { - + Device findDeviceById(TenantId tenantId, DeviceId deviceId); ListenableFuture findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); @@ -65,4 +65,6 @@ public interface DeviceService { ListenableFuture> findDeviceTypesByTenantId(TenantId tenantId); + Device swapDevice(TenantId tenantId, Device device); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index 1768e040d5..99f5e8d537 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -41,4 +41,6 @@ public interface EventService { List findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit); + void removeEvents(TenantId tenantId, EntityId entityId); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java index a54eecf55e..2e909b3460 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/relation/RelationService.java @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import java.util.List; -import java.util.concurrent.ExecutionException; /** * Created by ashvayka on 27.04.17. @@ -77,6 +76,8 @@ public interface RelationService { ListenableFuture> findInfoByQuery(TenantId tenantId, EntityRelationsQuery query); + void removeRelations(TenantId tenantId, EntityId entityId); + // TODO: This method may be useful for some validations in the future // ListenableFuture checkRecursiveRelation(EntityId from, EntityId to); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index afb0dbeba6..22b49552c0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -57,6 +57,8 @@ public class DataConstants { public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED"; public static final String ALARM_ACK = "ALARM_ACK"; public static final String ALARM_CLEAR = "ALARM_CLEAR"; + public static final String ENTITY_SWAPPED_FROM = "ENTITY_SWAPPED_FROM"; + public static final String ENTITY_SWAPPED_TO = "ENTITY_SWAPPED_TO"; public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index 03aeeba1d5..70909dc331 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -40,7 +40,9 @@ public enum ActionType { ALARM_CLEAR(false), LOGIN(false), LOGOUT(false), - LOCKOUT(false); + LOCKOUT(false), + SWAPPED_FROM_TENANT(false), + SWAPPED_TO_TENANT(false); private final boolean isRead; diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java index ba6b6841fb..f4efc2827f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogDao.java @@ -22,11 +22,12 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.dao.Dao; import java.util.List; import java.util.UUID; -public interface AuditLogDao { +public interface AuditLogDao extends Dao { ListenableFuture saveByTenantId(AuditLog auditLog); diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index 902162ff84..72b8272c69 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.audit.ActionStatus; @@ -38,7 +37,6 @@ import org.thingsboard.server.common.data.id.AuditLogId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.page.TimePageData; @@ -53,6 +51,8 @@ import org.thingsboard.server.dao.service.DataValidator; import java.io.PrintWriter; import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import static org.thingsboard.server.dao.service.Validator.validateEntityId; @@ -117,8 +117,8 @@ public class AuditLogServiceImpl implements AuditLogService { @Override public ListenableFuture> - logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, - ActionType actionType, Exception e, Object... additionalInfo) { + logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, + ActionType actionType, Exception e, Object... additionalInfo) { if (canLog(entityId.getEntityType(), actionType)) { JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo); ActionStatus actionStatus = ActionStatus.SUCCESS; @@ -129,7 +129,8 @@ public class AuditLogServiceImpl implements AuditLogService { } else { try { entityName = entityService.fetchEntityNameAsync(tenantId, entityId).get(); - } catch (Exception ex) {} + } catch (Exception ex) { + } } if (e != null) { actionStatus = ActionStatus.FAILURE; @@ -157,16 +158,36 @@ public class AuditLogServiceImpl implements AuditLogService { } } + @Override + public void removeAuditLogs(TenantId tenantId, EntityId entityId) { + List auditLogs = new ArrayList<>(); + TimePageData auditLogPageData; + TimePageLink auditLogPageLink = new TimePageLink(1000); + do { + auditLogPageData = findAuditLogsByTenantIdAndEntityId(tenantId, entityId, + new ArrayList<>(Arrays.asList(ActionType.values())), auditLogPageLink); + auditLogs.addAll(auditLogPageData.getData()); + if (auditLogPageData.hasNext()) { + auditLogPageLink = auditLogPageData.getNextPageLink(); + } + } while (auditLogPageData.hasNext()); + + for (AuditLog auditLog : auditLogs) { + auditLogDao.removeById(tenantId, auditLog.getUuidId()); + } + } + private JsonNode constructActionData(I entityId, E entity, - ActionType actionType, - Object... additionalInfo) { + ActionType actionType, + Object... additionalInfo) { ObjectNode actionData = objectMapper.createObjectNode(); - switch(actionType) { + switch (actionType) { case ADDED: case UPDATED: case ALARM_ACK: case ALARM_CLEAR: case RELATIONS_DELETED: + case SWAPPED_TO_TENANT: if (entity != null) { ObjectNode entityNode = objectMapper.valueToTree(entity); if (entityId.getEntityType() == EntityType.DASHBOARD) { @@ -208,7 +229,7 @@ public class AuditLogServiceImpl implements AuditLogService { scope = extractParameter(String.class, 0, additionalInfo); actionData.put("scope", scope); List keys = extractParameter(List.class, 1, additionalInfo); - ArrayNode attrsArrayNode = actionData.putArray("attributes"); + ArrayNode attrsArrayNode = actionData.putArray("attributes"); if (keys != null) { keys.forEach(attrsArrayNode::add); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java index 93d0a5ca4e..c677b4368b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java @@ -18,14 +18,12 @@ package org.thingsboard.server.dao.audit; import com.google.common.util.concurrent.ListenableFuture; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; @@ -61,4 +59,7 @@ public class DummyAuditLogServiceImpl implements AuditLogService { return null; } + @Override + public void removeAuditLogs(TenantId tenantId, EntityId entityId) { + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java index a01725fe52..55f83a4aae 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/CassandraDeviceDao.java @@ -189,4 +189,14 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) { + return findByIdAsync(tenantId, id); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 930c5a5126..4d5cf09775 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -115,4 +115,21 @@ public interface DeviceDao extends Dao { * @return the list of tenant device type objects */ ListenableFuture> findTenantDeviceTypesAsync(UUID tenantId); + + /** + * Find devices by tenantId and device id. + * @param tenantId the tenant Id + * @param id the device Id + * @return the device object + */ + Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id); + + /** + * Find devices by tenantId and device id. + * @param tenantId tenantId the tenantId + * @param id the deviceId + * @return the device object + */ + ListenableFuture findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index db998b7328..6b27e29425 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -28,6 +28,8 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; @@ -46,9 +48,11 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -97,18 +101,32 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Autowired private CacheManager cacheManager; + @Autowired + private EventService eventService; + + @Autowired + private AuditLogService auditLogService; + @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - return deviceDao.findById(tenantId, deviceId.getId()); + if (TenantId.SYS_TENANT_ID.equals(tenantId)) { + return deviceDao.findById(tenantId, deviceId.getId()); + } else { + return deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId()); + } } @Override public ListenableFuture findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); - return deviceDao.findByIdAsync(tenantId, deviceId.getId()); + if (TenantId.SYS_TENANT_ID.equals(tenantId)) { + return deviceDao.findByIdAsync(tenantId, deviceId.getId()); + } else { + return deviceDao.findDeviceByTenantIdAndIdAsync(tenantId, deviceId.getId()); + } } @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #name}") @@ -317,6 +335,33 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe }, MoreExecutors.directExecutor()); } + @Transactional + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") + @Override + public Device swapDevice(TenantId tenantId, Device device) { + log.trace("Executing swapDevice [{}]", device); + + try { + List entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get(); + if (!CollectionUtils.isEmpty(entityViews)) { + throw new DataValidationException("Can't swap device that is assigned to entity views!"); + } + } catch (ExecutionException | InterruptedException e) { + log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e); + throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e); + } + + eventService.removeEvents(device.getTenantId(), device.getId()); + + relationService.removeRelations(device.getTenantId(), device.getId()); + + auditLogService.removeAuditLogs(device.getTenantId(), device.getId()); + + device.setTenantId(tenantId); + device.setCustomerId(null); + return doSaveDevice(device, null); + } + private DataValidator deviceValidator = new DataValidator() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 0f777d889f..50d49e75ad 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -94,6 +95,24 @@ public class BaseEventService implements EventService { return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit); } + @Override + public void removeEvents(TenantId tenantId, EntityId entityId) { + List events = new ArrayList<>(); + TimePageData eventPageData; + TimePageLink eventPageLink = new TimePageLink(1000); + do { + eventPageData = findEvents(tenantId, entityId, eventPageLink); + events.addAll(eventPageData.getData()); + if (eventPageData.hasNext()) { + eventPageLink = eventPageData.getNextPageLink(); + } + } while (eventPageData.hasNext()); + + for (Event event : events) { + eventDao.removeById(tenantId, event.getUuidId()); + } + } + private DataValidator eventValidator = new DataValidator() { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 22b2543489..53ca684517 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -506,6 +506,22 @@ public class BaseRelationService implements RelationService { }, MoreExecutors.directExecutor()); } + @Override + public void removeRelations(TenantId tenantId, EntityId entityId) { + Cache cache = cacheManager.getCache(RELATIONS_CACHE); + + List relations = new ArrayList<>(); + for (RelationTypeGroup relationTypeGroup : RelationTypeGroup.values()) { + relations.addAll(findByFrom(tenantId, entityId, relationTypeGroup)); + relations.addAll(findByTo(tenantId, entityId, relationTypeGroup)); + } + + for (EntityRelation relation : relations) { + cacheEviction(relation, cache); + deleteRelation(tenantId, relation); + } + } + protected void validate(EntityRelation relation) { if (relation == null) { throw new DataValidationException("Relation type should be specified!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 16ff6f1b52..baf34dc59b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -85,4 +85,7 @@ public interface DeviceRepository extends CrudRepository { List findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List deviceIds); List findDevicesByTenantIdAndIdIn(String tenantId, List deviceIds); + + DeviceEntity findByTenantIdAndId(String tenantId, String id); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 34d59036a0..57d0ef6bb4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -138,6 +138,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); } + @Override + public Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id) { + return DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id))); + } + + @Override + public ListenableFuture findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) { + return service.submit(() -> DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id)))); + } + private List convertTenantDeviceTypesToDto(UUID tenantId, List types) { List list = Collections.emptyList(); if (types != null && !types.isEmpty()) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 07a0508a87..62e75cd72a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -16,8 +16,13 @@ package org.thingsboard.rule.engine.filter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; -import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -30,7 +35,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; configClazz = EmptyNodeConfiguration.class, relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", - "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other"}, + "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Swapped From", "Entity Swapped To"}, nodeDescription = "Route incoming messages by Message Type", nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\" etc. via corresponding chain, otherwise Other chain is used.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -81,6 +86,10 @@ public class TbMsgTypeSwitchNode implements TbNode { relationType = "Alarm Cleared"; } else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) { relationType = "RPC Request to Device"; + } else if (msg.getType().equals(DataConstants.ENTITY_SWAPPED_FROM)) { + relationType = "Entity Swapped From"; + } else if (msg.getType().equals(DataConstants.ENTITY_SWAPPED_TO)) { + relationType = "Entity Swapped To"; } else { relationType = "Other"; } diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js index 8285430c56..254313b7a0 100644 --- a/ui/src/app/common/types.constant.js +++ b/ui/src/app/common/types.constant.js @@ -222,6 +222,12 @@ export default angular.module('thingsboard.types', []) }, "LOCKOUT": { name: "audit-log.type-lockout" + }, + "SWAPPED_FROM_TENANT": { + name: "audit-log.type-swapped-from-tenant" + }, + "SWAPPED_TO_TENANT": { + name: "audit-log.type-swapped-to-tenant" } }, auditLogActionStatus: { diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index f6172d585f..7e81fd1a57 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -356,7 +356,9 @@ "action-data": "Action data", "failure-details": "Failure details", "search": "Search audit logs", - "clear-search": "Clear search" + "clear-search": "Clear search", + "type-swapped-from-tenant": "Swapped from Tenant", + "type-swapped-to-tenant": "Swapped to Tenant" }, "confirm-on-exit": { "message": "You have unsaved changes. Are you sure you want to leave this page?",