Commit ac007bea24ef4244190690a173c6ef323cb0258e
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 | } | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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?", | ... | ... |