Commit ac007bea24ef4244190690a173c6ef323cb0258e

Authored by Dima Landiak
Committed by Andrew Shvayka
1 parent a400bdb9

device swap feature init

Showing 21 changed files with 272 additions and 24 deletions
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.core.JsonProcessingException;
18 19 import com.fasterxml.jackson.databind.ObjectMapper;
19 20 import com.fasterxml.jackson.databind.node.ArrayNode;
20 21 import com.fasterxml.jackson.databind.node.ObjectNode;
... ... @@ -26,7 +27,6 @@ import org.springframework.beans.factory.annotation.Value;
26 27 import org.springframework.security.core.Authentication;
27 28 import org.springframework.security.core.context.SecurityContextHolder;
28 29 import org.springframework.web.bind.annotation.ExceptionHandler;
29   -import org.thingsboard.server.common.data.BaseData;
30 30 import org.thingsboard.server.common.data.Customer;
31 31 import org.thingsboard.server.common.data.Dashboard;
32 32 import org.thingsboard.server.common.data.DashboardInfo;
... ... @@ -39,12 +39,12 @@ import org.thingsboard.server.common.data.HasTenantId;
39 39 import org.thingsboard.server.common.data.Tenant;
40 40 import org.thingsboard.server.common.data.User;
41 41 import org.thingsboard.server.common.data.alarm.Alarm;
42   -import org.thingsboard.server.common.data.id.AlarmId;
43 42 import org.thingsboard.server.common.data.alarm.AlarmInfo;
44 43 import org.thingsboard.server.common.data.asset.Asset;
45 44 import org.thingsboard.server.common.data.audit.ActionType;
46 45 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
47 46 import org.thingsboard.server.common.data.exception.ThingsboardException;
  47 +import org.thingsboard.server.common.data.id.AlarmId;
48 48 import org.thingsboard.server.common.data.id.AssetId;
49 49 import org.thingsboard.server.common.data.id.CustomerId;
50 50 import org.thingsboard.server.common.data.id.DashboardId;
... ... @@ -595,6 +595,12 @@ public abstract class BaseController {
595 595 case ALARM_CLEAR:
596 596 msgType = DataConstants.ALARM_CLEAR;
597 597 break;
  598 + case SWAPPED_FROM_TENANT:
  599 + msgType = DataConstants.ENTITY_SWAPPED_FROM;
  600 + break;
  601 + case SWAPPED_TO_TENANT:
  602 + msgType = DataConstants.ENTITY_SWAPPED_TO;
  603 + break;
598 604 }
599 605 if (!StringUtils.isEmpty(msgType)) {
600 606 try {
... ... @@ -614,6 +620,16 @@ public abstract class BaseController {
614 620 String strCustomerName = extractParameter(String.class, 2, additionalInfo);
615 621 metaData.putValue("unassignedCustomerId", strCustomerId);
616 622 metaData.putValue("unassignedCustomerName", strCustomerName);
  623 + } else if (actionType == ActionType.SWAPPED_FROM_TENANT) {
  624 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  625 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  626 + metaData.putValue("swappedFromTenantId", strTenantId);
  627 + metaData.putValue("swappedFromTenantName", strTenantName);
  628 + } else if (actionType == ActionType.SWAPPED_TO_TENANT) {
  629 + String strTenantId = extractParameter(String.class, 0, additionalInfo);
  630 + String strTenantName = extractParameter(String.class, 1, additionalInfo);
  631 + metaData.putValue("swappedToTenantId", strTenantId);
  632 + metaData.putValue("swappedToTenantName", strTenantName);
617 633 }
618 634 ObjectNode entityNode;
619 635 if (entity != null) {
... ... @@ -677,5 +693,13 @@ public abstract class BaseController {
677 693 return result;
678 694 }
679 695
  696 + protected <E extends HasName> String entityToStr(E entity) {
  697 + try {
  698 + return json.writeValueAsString(json.valueToTree(entity));
  699 + } catch (JsonProcessingException e) {
  700 + log.warn("[{}] Failed to convert entity to string!", entity, e);
  701 + }
  702 + return null;
  703 + }
680 704
681 705 }
... ...
... ... @@ -39,8 +39,10 @@ import org.thingsboard.server.common.data.DataConstants;
39 39 import org.thingsboard.server.common.data.Device;
40 40 import org.thingsboard.server.common.data.EntitySubtype;
41 41 import org.thingsboard.server.common.data.EntityType;
  42 +import org.thingsboard.server.common.data.Tenant;
42 43 import org.thingsboard.server.common.data.audit.ActionType;
43 44 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
  45 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
44 46 import org.thingsboard.server.common.data.exception.ThingsboardException;
45 47 import org.thingsboard.server.common.data.id.CustomerId;
46 48 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -48,6 +50,9 @@ import org.thingsboard.server.common.data.id.TenantId;
48 50 import org.thingsboard.server.common.data.page.TextPageData;
49 51 import org.thingsboard.server.common.data.page.TextPageLink;
50 52 import org.thingsboard.server.common.data.security.DeviceCredentials;
  53 +import org.thingsboard.server.common.msg.TbMsg;
  54 +import org.thingsboard.server.common.msg.TbMsgDataType;
  55 +import org.thingsboard.server.common.msg.TbMsgMetaData;
51 56 import org.thingsboard.server.dao.device.claim.ClaimResponse;
52 57 import org.thingsboard.server.dao.device.claim.ClaimResult;
53 58 import org.thingsboard.server.dao.exception.IncorrectParameterException;
... ... @@ -70,6 +75,7 @@ public class DeviceController extends BaseController {
70 75
71 76 private static final String DEVICE_ID = "deviceId";
72 77 private static final String DEVICE_NAME = "deviceName";
  78 + private static final String TENANT_ID = "tenantId";
73 79
74 80 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
75 81 @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET)
... ... @@ -481,4 +487,54 @@ public class DeviceController extends BaseController {
481 487 }
482 488 return DataConstants.DEFAULT_SECRET_KEY;
483 489 }
  490 +
  491 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  492 + @RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST)
  493 + @ResponseBody
  494 + public Device swapDevice(@PathVariable(TENANT_ID) String strTenantId,
  495 + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
  496 + checkParameter(TENANT_ID, strTenantId);
  497 + checkParameter(DEVICE_ID, strDeviceId);
  498 + try {
  499 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  500 + Device device = checkDeviceId(deviceId, Operation.WRITE);
  501 +
  502 + TenantId newTenantId = new TenantId(toUUID(strTenantId));
  503 + Tenant newTenant = tenantService.findTenantById(newTenantId);
  504 + if (newTenant == null) {
  505 + throw new ThingsboardException("Could not find the specified Tenant!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  506 + }
  507 +
  508 + Device swappedDevice = deviceService.swapDevice(newTenantId, device);
  509 +
  510 + logEntityAction(getCurrentUser(), deviceId, swappedDevice,
  511 + swappedDevice.getCustomerId(),
  512 + ActionType.SWAPPED_TO_TENANT, null, strTenantId, newTenant.getName());
  513 +
  514 + Tenant currentTenant = tenantService.findTenantById(getTenantId());
  515 + pushSwappedFromNotification(currentTenant, newTenantId, swappedDevice);
  516 +
  517 + return swappedDevice;
  518 + } catch (Exception e) {
  519 + logEntityAction(getCurrentUser(), emptyId(EntityType.DEVICE), null,
  520 + null,
  521 + ActionType.SWAPPED_TO_TENANT, e, strTenantId);
  522 + throw handleException(e);
  523 + }
  524 + }
  525 +
  526 + private void pushSwappedFromNotification(Tenant currentTenant, TenantId newTenantId, Device swappedDevice) {
  527 + String data = entityToStr(swappedDevice);
  528 + if (data != null) {
  529 + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_SWAPPED_FROM, swappedDevice.getId(), getMetaDataForSwappedFrom(currentTenant), TbMsgDataType.JSON, data);
  530 + tbClusterService.pushMsgToRuleEngine(newTenantId, swappedDevice.getId(), tbMsg, null);
  531 + }
  532 + }
  533 +
  534 + private TbMsgMetaData getMetaDataForSwappedFrom(Tenant tenant) {
  535 + TbMsgMetaData metaData = new TbMsgMetaData();
  536 + metaData.putValue("swappedFromTenantId", tenant.getId().getId().toString());
  537 + metaData.putValue("swappedFromTenantName", tenant.getName());
  538 + return metaData;
  539 + }
484 540 }
... ...
... ... @@ -16,14 +16,12 @@
16 16 package org.thingsboard.server.dao.audit;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19   -import org.thingsboard.server.common.data.BaseData;
20 19 import org.thingsboard.server.common.data.HasName;
21 20 import org.thingsboard.server.common.data.audit.ActionType;
22 21 import org.thingsboard.server.common.data.audit.AuditLog;
23 22 import org.thingsboard.server.common.data.id.CustomerId;
24 23 import org.thingsboard.server.common.data.id.EntityId;
25 24 import org.thingsboard.server.common.data.id.TenantId;
26   -import org.thingsboard.server.common.data.id.UUIDBased;
27 25 import org.thingsboard.server.common.data.id.UserId;
28 26 import org.thingsboard.server.common.data.page.TimePageData;
29 27 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -50,4 +48,5 @@ public interface AuditLogService {
50 48 ActionType actionType,
51 49 Exception e, Object... additionalInfo);
52 50
  51 + void removeAuditLogs(TenantId tenantId, EntityId entityId);
53 52 }
... ...
... ... @@ -28,7 +28,7 @@ import org.thingsboard.server.common.data.page.TextPageLink;
28 28 import java.util.List;
29 29
30 30 public interface DeviceService {
31   -
  31 +
32 32 Device findDeviceById(TenantId tenantId, DeviceId deviceId);
33 33
34 34 ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId);
... ... @@ -65,4 +65,6 @@ public interface DeviceService {
65 65
66 66 ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId);
67 67
  68 + Device swapDevice(TenantId tenantId, Device device);
  69 +
68 70 }
... ...
... ... @@ -41,4 +41,6 @@ public interface EventService {
41 41
42 42 List<Event> findLatestEvents(TenantId tenantId, EntityId entityId, String eventType, int limit);
43 43
  44 + void removeEvents(TenantId tenantId, EntityId entityId);
  45 +
44 46 }
... ...
... ... @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
24 24 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
25 25
26 26 import java.util.List;
27   -import java.util.concurrent.ExecutionException;
28 27
29 28 /**
30 29 * Created by ashvayka on 27.04.17.
... ... @@ -77,6 +76,8 @@ public interface RelationService {
77 76
78 77 ListenableFuture<List<EntityRelationInfo>> findInfoByQuery(TenantId tenantId, EntityRelationsQuery query);
79 78
  79 + void removeRelations(TenantId tenantId, EntityId entityId);
  80 +
80 81 // TODO: This method may be useful for some validations in the future
81 82 // ListenableFuture<Boolean> checkRecursiveRelation(EntityId from, EntityId to);
82 83
... ...
... ... @@ -57,6 +57,8 @@ public class DataConstants {
57 57 public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED";
58 58 public static final String ALARM_ACK = "ALARM_ACK";
59 59 public static final String ALARM_CLEAR = "ALARM_CLEAR";
  60 + public static final String ENTITY_SWAPPED_FROM = "ENTITY_SWAPPED_FROM";
  61 + public static final String ENTITY_SWAPPED_TO = "ENTITY_SWAPPED_TO";
60 62
61 63 public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
62 64
... ...
... ... @@ -40,7 +40,9 @@ public enum ActionType {
40 40 ALARM_CLEAR(false),
41 41 LOGIN(false),
42 42 LOGOUT(false),
43   - LOCKOUT(false);
  43 + LOCKOUT(false),
  44 + SWAPPED_FROM_TENANT(false),
  45 + SWAPPED_TO_TENANT(false);
44 46
45 47 private final boolean isRead;
46 48
... ...
... ... @@ -22,11 +22,12 @@ import org.thingsboard.server.common.data.id.CustomerId;
22 22 import org.thingsboard.server.common.data.id.EntityId;
23 23 import org.thingsboard.server.common.data.id.UserId;
24 24 import org.thingsboard.server.common.data.page.TimePageLink;
  25 +import org.thingsboard.server.dao.Dao;
25 26
26 27 import java.util.List;
27 28 import java.util.UUID;
28 29
29   -public interface AuditLogDao {
  30 +public interface AuditLogDao extends Dao<AuditLog> {
30 31
31 32 ListenableFuture<Void> saveByTenantId(AuditLog auditLog);
32 33
... ...
... ... @@ -28,7 +28,6 @@ import org.springframework.beans.factory.annotation.Autowired;
28 28 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
29 29 import org.springframework.stereotype.Service;
30 30 import org.springframework.util.StringUtils;
31   -import org.thingsboard.server.common.data.BaseData;
32 31 import org.thingsboard.server.common.data.EntityType;
33 32 import org.thingsboard.server.common.data.HasName;
34 33 import org.thingsboard.server.common.data.audit.ActionStatus;
... ... @@ -38,7 +37,6 @@ import org.thingsboard.server.common.data.id.AuditLogId;
38 37 import org.thingsboard.server.common.data.id.CustomerId;
39 38 import org.thingsboard.server.common.data.id.EntityId;
40 39 import org.thingsboard.server.common.data.id.TenantId;
41   -import org.thingsboard.server.common.data.id.UUIDBased;
42 40 import org.thingsboard.server.common.data.id.UserId;
43 41 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
44 42 import org.thingsboard.server.common.data.page.TimePageData;
... ... @@ -53,6 +51,8 @@ import org.thingsboard.server.dao.service.DataValidator;
53 51
54 52 import java.io.PrintWriter;
55 53 import java.io.StringWriter;
  54 +import java.util.ArrayList;
  55 +import java.util.Arrays;
56 56 import java.util.List;
57 57
58 58 import static org.thingsboard.server.dao.service.Validator.validateEntityId;
... ... @@ -117,8 +117,8 @@ public class AuditLogServiceImpl implements AuditLogService {
117 117
118 118 @Override
119 119 public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>>
120   - logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
121   - ActionType actionType, Exception e, Object... additionalInfo) {
  120 + logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
  121 + ActionType actionType, Exception e, Object... additionalInfo) {
122 122 if (canLog(entityId.getEntityType(), actionType)) {
123 123 JsonNode actionData = constructActionData(entityId, entity, actionType, additionalInfo);
124 124 ActionStatus actionStatus = ActionStatus.SUCCESS;
... ... @@ -129,7 +129,8 @@ public class AuditLogServiceImpl implements AuditLogService {
129 129 } else {
130 130 try {
131 131 entityName = entityService.fetchEntityNameAsync(tenantId, entityId).get();
132   - } catch (Exception ex) {}
  132 + } catch (Exception ex) {
  133 + }
133 134 }
134 135 if (e != null) {
135 136 actionStatus = ActionStatus.FAILURE;
... ... @@ -157,16 +158,36 @@ public class AuditLogServiceImpl implements AuditLogService {
157 158 }
158 159 }
159 160
  161 + @Override
  162 + public void removeAuditLogs(TenantId tenantId, EntityId entityId) {
  163 + List<AuditLog> auditLogs = new ArrayList<>();
  164 + TimePageData<AuditLog> auditLogPageData;
  165 + TimePageLink auditLogPageLink = new TimePageLink(1000);
  166 + do {
  167 + auditLogPageData = findAuditLogsByTenantIdAndEntityId(tenantId, entityId,
  168 + new ArrayList<>(Arrays.asList(ActionType.values())), auditLogPageLink);
  169 + auditLogs.addAll(auditLogPageData.getData());
  170 + if (auditLogPageData.hasNext()) {
  171 + auditLogPageLink = auditLogPageData.getNextPageLink();
  172 + }
  173 + } while (auditLogPageData.hasNext());
  174 +
  175 + for (AuditLog auditLog : auditLogs) {
  176 + auditLogDao.removeById(tenantId, auditLog.getUuidId());
  177 + }
  178 + }
  179 +
160 180 private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity,
161   - ActionType actionType,
162   - Object... additionalInfo) {
  181 + ActionType actionType,
  182 + Object... additionalInfo) {
163 183 ObjectNode actionData = objectMapper.createObjectNode();
164   - switch(actionType) {
  184 + switch (actionType) {
165 185 case ADDED:
166 186 case UPDATED:
167 187 case ALARM_ACK:
168 188 case ALARM_CLEAR:
169 189 case RELATIONS_DELETED:
  190 + case SWAPPED_TO_TENANT:
170 191 if (entity != null) {
171 192 ObjectNode entityNode = objectMapper.valueToTree(entity);
172 193 if (entityId.getEntityType() == EntityType.DASHBOARD) {
... ... @@ -208,7 +229,7 @@ public class AuditLogServiceImpl implements AuditLogService {
208 229 scope = extractParameter(String.class, 0, additionalInfo);
209 230 actionData.put("scope", scope);
210 231 List<String> keys = extractParameter(List.class, 1, additionalInfo);
211   - ArrayNode attrsArrayNode = actionData.putArray("attributes");
  232 + ArrayNode attrsArrayNode = actionData.putArray("attributes");
212 233 if (keys != null) {
213 234 keys.forEach(attrsArrayNode::add);
214 235 }
... ...
... ... @@ -18,14 +18,12 @@ package org.thingsboard.server.dao.audit;
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
20 20 import org.springframework.stereotype.Service;
21   -import org.thingsboard.server.common.data.BaseData;
22 21 import org.thingsboard.server.common.data.HasName;
23 22 import org.thingsboard.server.common.data.audit.ActionType;
24 23 import org.thingsboard.server.common.data.audit.AuditLog;
25 24 import org.thingsboard.server.common.data.id.CustomerId;
26 25 import org.thingsboard.server.common.data.id.EntityId;
27 26 import org.thingsboard.server.common.data.id.TenantId;
28   -import org.thingsboard.server.common.data.id.UUIDBased;
29 27 import org.thingsboard.server.common.data.id.UserId;
30 28 import org.thingsboard.server.common.data.page.TimePageData;
31 29 import org.thingsboard.server.common.data.page.TimePageLink;
... ... @@ -61,4 +59,7 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
61 59 return null;
62 60 }
63 61
  62 + @Override
  63 + public void removeAuditLogs(TenantId tenantId, EntityId entityId) {
  64 + }
64 65 }
... ...
... ... @@ -189,4 +189,14 @@ public class CassandraDeviceDao extends CassandraAbstractSearchTextDao<DeviceEnt
189 189 }, MoreExecutors.directExecutor());
190 190 }
191 191
  192 + @Override
  193 + public Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id) {
  194 + return findById(tenantId, id);
  195 + }
  196 +
  197 + @Override
  198 + public ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) {
  199 + return findByIdAsync(tenantId, id);
  200 + }
  201 +
192 202 }
... ...
... ... @@ -115,4 +115,21 @@ public interface DeviceDao extends Dao<Device> {
115 115 * @return the list of tenant device type objects
116 116 */
117 117 ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId);
  118 +
  119 + /**
  120 + * Find devices by tenantId and device id.
  121 + * @param tenantId the tenant Id
  122 + * @param id the device Id
  123 + * @return the device object
  124 + */
  125 + Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id);
  126 +
  127 + /**
  128 + * Find devices by tenantId and device id.
  129 + * @param tenantId tenantId the tenantId
  130 + * @param id the deviceId
  131 + * @return the device object
  132 + */
  133 + ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id);
  134 +
118 135 }
... ...
... ... @@ -28,6 +28,8 @@ import org.springframework.cache.CacheManager;
28 28 import org.springframework.cache.annotation.CacheEvict;
29 29 import org.springframework.cache.annotation.Cacheable;
30 30 import org.springframework.stereotype.Service;
  31 +import org.springframework.transaction.annotation.Transactional;
  32 +import org.springframework.util.CollectionUtils;
31 33 import org.springframework.util.StringUtils;
32 34 import org.thingsboard.server.common.data.Customer;
33 35 import org.thingsboard.server.common.data.Device;
... ... @@ -46,9 +48,11 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
46 48 import org.thingsboard.server.common.data.relation.EntitySearchDirection;
47 49 import org.thingsboard.server.common.data.security.DeviceCredentials;
48 50 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
  51 +import org.thingsboard.server.dao.audit.AuditLogService;
49 52 import org.thingsboard.server.dao.customer.CustomerDao;
50 53 import org.thingsboard.server.dao.entity.AbstractEntityService;
51 54 import org.thingsboard.server.dao.entityview.EntityViewService;
  55 +import org.thingsboard.server.dao.event.EventService;
52 56 import org.thingsboard.server.dao.exception.DataValidationException;
53 57 import org.thingsboard.server.dao.service.DataValidator;
54 58 import org.thingsboard.server.dao.service.PaginatedRemover;
... ... @@ -97,18 +101,32 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
97 101 @Autowired
98 102 private CacheManager cacheManager;
99 103
  104 + @Autowired
  105 + private EventService eventService;
  106 +
  107 + @Autowired
  108 + private AuditLogService auditLogService;
  109 +
100 110 @Override
101 111 public Device findDeviceById(TenantId tenantId, DeviceId deviceId) {
102 112 log.trace("Executing findDeviceById [{}]", deviceId);
103 113 validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
104   - return deviceDao.findById(tenantId, deviceId.getId());
  114 + if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
  115 + return deviceDao.findById(tenantId, deviceId.getId());
  116 + } else {
  117 + return deviceDao.findDeviceByTenantIdAndId(tenantId, deviceId.getId());
  118 + }
105 119 }
106 120
107 121 @Override
108 122 public ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId) {
109 123 log.trace("Executing findDeviceById [{}]", deviceId);
110 124 validateId(deviceId, INCORRECT_DEVICE_ID + deviceId);
111   - return deviceDao.findByIdAsync(tenantId, deviceId.getId());
  125 + if (TenantId.SYS_TENANT_ID.equals(tenantId)) {
  126 + return deviceDao.findByIdAsync(tenantId, deviceId.getId());
  127 + } else {
  128 + return deviceDao.findDeviceByTenantIdAndIdAsync(tenantId, deviceId.getId());
  129 + }
112 130 }
113 131
114 132 @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #name}")
... ... @@ -317,6 +335,33 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
317 335 }, MoreExecutors.directExecutor());
318 336 }
319 337
  338 + @Transactional
  339 + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}")
  340 + @Override
  341 + public Device swapDevice(TenantId tenantId, Device device) {
  342 + log.trace("Executing swapDevice [{}]", device);
  343 +
  344 + try {
  345 + List<EntityView> entityViews = entityViewService.findEntityViewsByTenantIdAndEntityIdAsync(device.getTenantId(), device.getId()).get();
  346 + if (!CollectionUtils.isEmpty(entityViews)) {
  347 + throw new DataValidationException("Can't swap device that is assigned to entity views!");
  348 + }
  349 + } catch (ExecutionException | InterruptedException e) {
  350 + log.error("Exception while finding entity views for deviceId [{}]", device.getId(), e);
  351 + throw new RuntimeException("Exception while finding entity views for deviceId [" + device.getId() + "]", e);
  352 + }
  353 +
  354 + eventService.removeEvents(device.getTenantId(), device.getId());
  355 +
  356 + relationService.removeRelations(device.getTenantId(), device.getId());
  357 +
  358 + auditLogService.removeAuditLogs(device.getTenantId(), device.getId());
  359 +
  360 + device.setTenantId(tenantId);
  361 + device.setCustomerId(null);
  362 + return doSaveDevice(device, null);
  363 + }
  364 +
320 365 private DataValidator<Device> deviceValidator =
321 366 new DataValidator<Device>() {
322 367
... ...
... ... @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
28 28 import org.thingsboard.server.dao.exception.DataValidationException;
29 29 import org.thingsboard.server.dao.service.DataValidator;
30 30
  31 +import java.util.ArrayList;
31 32 import java.util.List;
32 33 import java.util.Optional;
33 34
... ... @@ -94,6 +95,24 @@ public class BaseEventService implements EventService {
94 95 return eventDao.findLatestEvents(tenantId.getId(), entityId, eventType, limit);
95 96 }
96 97
  98 + @Override
  99 + public void removeEvents(TenantId tenantId, EntityId entityId) {
  100 + List<Event> events = new ArrayList<>();
  101 + TimePageData<Event> eventPageData;
  102 + TimePageLink eventPageLink = new TimePageLink(1000);
  103 + do {
  104 + eventPageData = findEvents(tenantId, entityId, eventPageLink);
  105 + events.addAll(eventPageData.getData());
  106 + if (eventPageData.hasNext()) {
  107 + eventPageLink = eventPageData.getNextPageLink();
  108 + }
  109 + } while (eventPageData.hasNext());
  110 +
  111 + for (Event event : events) {
  112 + eventDao.removeById(tenantId, event.getUuidId());
  113 + }
  114 + }
  115 +
97 116 private DataValidator<Event> eventValidator =
98 117 new DataValidator<Event>() {
99 118 @Override
... ...
... ... @@ -506,6 +506,22 @@ public class BaseRelationService implements RelationService {
506 506 }, MoreExecutors.directExecutor());
507 507 }
508 508
  509 + @Override
  510 + public void removeRelations(TenantId tenantId, EntityId entityId) {
  511 + Cache cache = cacheManager.getCache(RELATIONS_CACHE);
  512 +
  513 + List<EntityRelation> relations = new ArrayList<>();
  514 + for (RelationTypeGroup relationTypeGroup : RelationTypeGroup.values()) {
  515 + relations.addAll(findByFrom(tenantId, entityId, relationTypeGroup));
  516 + relations.addAll(findByTo(tenantId, entityId, relationTypeGroup));
  517 + }
  518 +
  519 + for (EntityRelation relation : relations) {
  520 + cacheEviction(relation, cache);
  521 + deleteRelation(tenantId, relation);
  522 + }
  523 + }
  524 +
509 525 protected void validate(EntityRelation relation) {
510 526 if (relation == null) {
511 527 throw new DataValidationException("Relation type should be specified!");
... ...
... ... @@ -85,4 +85,7 @@ public interface DeviceRepository extends CrudRepository<DeviceEntity, String> {
85 85 List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndIdIn(String tenantId, String customerId, List<String> deviceIds);
86 86
87 87 List<DeviceEntity> findDevicesByTenantIdAndIdIn(String tenantId, List<String> deviceIds);
  88 +
  89 + DeviceEntity findByTenantIdAndId(String tenantId, String id);
  90 +
88 91 }
... ...
... ... @@ -138,6 +138,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device>
138 138 return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId))));
139 139 }
140 140
  141 + @Override
  142 + public Device findDeviceByTenantIdAndId(TenantId tenantId, UUID id) {
  143 + return DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id)));
  144 + }
  145 +
  146 + @Override
  147 + public ListenableFuture<Device> findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id) {
  148 + return service.submit(() -> DaoUtil.getData(deviceRepository.findByTenantIdAndId(fromTimeUUID(tenantId.getId()), fromTimeUUID(id))));
  149 + }
  150 +
141 151 private List<EntitySubtype> convertTenantDeviceTypesToDto(UUID tenantId, List<String> types) {
142 152 List<EntitySubtype> list = Collections.emptyList();
143 153 if (types != null && !types.isEmpty()) {
... ...
... ... @@ -16,8 +16,13 @@
16 16 package org.thingsboard.rule.engine.filter;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.rule.engine.api.EmptyNodeConfiguration;
  20 +import org.thingsboard.rule.engine.api.RuleNode;
  21 +import org.thingsboard.rule.engine.api.TbContext;
  22 +import org.thingsboard.rule.engine.api.TbNode;
  23 +import org.thingsboard.rule.engine.api.TbNodeConfiguration;
  24 +import org.thingsboard.rule.engine.api.TbNodeException;
19 25 import org.thingsboard.rule.engine.api.util.TbNodeUtils;
20   -import org.thingsboard.rule.engine.api.*;
21 26 import org.thingsboard.server.common.data.DataConstants;
22 27 import org.thingsboard.server.common.data.plugin.ComponentType;
23 28 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -30,7 +35,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType;
30 35 configClazz = EmptyNodeConfiguration.class,
31 36 relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event",
32 37 "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned",
33   - "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other"},
  38 + "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Swapped From", "Entity Swapped To"},
34 39 nodeDescription = "Route incoming messages by Message Type",
35 40 nodeDetails = "Sends messages with message types <b>\"Post attributes\", \"Post telemetry\", \"RPC Request\"</b> etc. via corresponding chain, otherwise <b>Other</b> chain is used.",
36 41 uiResources = {"static/rulenode/rulenode-core-config.js"},
... ... @@ -81,6 +86,10 @@ public class TbMsgTypeSwitchNode implements TbNode {
81 86 relationType = "Alarm Cleared";
82 87 } else if (msg.getType().equals(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE)) {
83 88 relationType = "RPC Request to Device";
  89 + } else if (msg.getType().equals(DataConstants.ENTITY_SWAPPED_FROM)) {
  90 + relationType = "Entity Swapped From";
  91 + } else if (msg.getType().equals(DataConstants.ENTITY_SWAPPED_TO)) {
  92 + relationType = "Entity Swapped To";
84 93 } else {
85 94 relationType = "Other";
86 95 }
... ...
... ... @@ -222,6 +222,12 @@ export default angular.module('thingsboard.types', [])
222 222 },
223 223 "LOCKOUT": {
224 224 name: "audit-log.type-lockout"
  225 + },
  226 + "SWAPPED_FROM_TENANT": {
  227 + name: "audit-log.type-swapped-from-tenant"
  228 + },
  229 + "SWAPPED_TO_TENANT": {
  230 + name: "audit-log.type-swapped-to-tenant"
225 231 }
226 232 },
227 233 auditLogActionStatus: {
... ...
... ... @@ -356,7 +356,9 @@
356 356 "action-data": "Action data",
357 357 "failure-details": "Failure details",
358 358 "search": "Search audit logs",
359   - "clear-search": "Clear search"
  359 + "clear-search": "Clear search",
  360 + "type-swapped-from-tenant": "Swapped from Tenant",
  361 + "type-swapped-to-tenant": "Swapped to Tenant"
360 362 },
361 363 "confirm-on-exit": {
362 364 "message": "You have unsaved changes. Are you sure you want to leave this page?",
... ...