Commit 451c15874259f26c71d7d98466f5d839f01ec09b

Authored by Volodymyr Babak
1 parent e7d00f4b

Added EDGE backend support

Showing 56 changed files with 4096 additions and 44 deletions

Too many changes to show.

To preserve performance only 56 of 112 files are displayed.

@@ -89,6 +89,10 @@ @@ -89,6 +89,10 @@
89 <artifactId>queue</artifactId> 89 <artifactId>queue</artifactId>
90 </dependency> 90 </dependency>
91 <dependency> 91 <dependency>
  92 + <groupId>org.thingsboard.common</groupId>
  93 + <artifactId>edge-api</artifactId>
  94 + </dependency>
  95 + <dependency>
92 <groupId>org.thingsboard</groupId> 96 <groupId>org.thingsboard</groupId>
93 <artifactId>dao</artifactId> 97 <artifactId>dao</artifactId>
94 <type>test-jar</type> 98 <type>test-jar</type>
@@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
2 "ruleChain": { 2 "ruleChain": {
3 "additionalInfo": null, 3 "additionalInfo": null,
4 "name": "Root Rule Chain", 4 "name": "Root Rule Chain",
  5 + "type": "SYSTEM",
5 "firstRuleNodeId": null, 6 "firstRuleNodeId": null,
6 "root": true, 7 "root": true,
7 "debugMode": false, 8 "debugMode": false,
  1 +--
  2 +-- Copyright © 2016-2020 The Thingsboard Authors
  3 +--
  4 +-- Licensed under the Apache License, Version 2.0 (the "License");
  5 +-- you may not use this file except in compliance with the License.
  6 +-- You may obtain a copy of the License at
  7 +--
  8 +-- http://www.apache.org/licenses/LICENSE-2.0
  9 +--
  10 +-- Unless required by applicable law or agreed to in writing, software
  11 +-- distributed under the License is distributed on an "AS IS" BASIS,
  12 +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +-- See the License for the specific language governing permissions and
  14 +-- limitations under the License.
  15 +--
  16 +
  17 +CREATE TABLE IF NOT EXISTS edge (
  18 + id varchar(31) NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
  19 + additional_info varchar,
  20 + customer_id varchar(31),
  21 + root_rule_chain_id varchar(31),
  22 + configuration varchar(10000000),
  23 + type varchar(255),
  24 + name varchar(255),
  25 + label varchar(255),
  26 + routing_key varchar(255),
  27 + secret varchar(255),
  28 + search_text varchar(255),
  29 + tenant_id varchar(31)
  30 +);
@@ -56,6 +56,7 @@ import org.thingsboard.server.dao.cassandra.CassandraCluster; @@ -56,6 +56,7 @@ import org.thingsboard.server.dao.cassandra.CassandraCluster;
56 import org.thingsboard.server.dao.customer.CustomerService; 56 import org.thingsboard.server.dao.customer.CustomerService;
57 import org.thingsboard.server.dao.dashboard.DashboardService; 57 import org.thingsboard.server.dao.dashboard.DashboardService;
58 import org.thingsboard.server.dao.device.DeviceService; 58 import org.thingsboard.server.dao.device.DeviceService;
  59 +import org.thingsboard.server.dao.edge.EdgeService;
59 import org.thingsboard.server.dao.entityview.EntityViewService; 60 import org.thingsboard.server.dao.entityview.EntityViewService;
60 import org.thingsboard.server.dao.event.EventService; 61 import org.thingsboard.server.dao.event.EventService;
61 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor; 62 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
@@ -248,6 +249,11 @@ public class ActorSystemContext { @@ -248,6 +249,11 @@ public class ActorSystemContext {
248 @Getter 249 @Getter
249 private RuleChainTransactionService ruleChainTransactionService; 250 private RuleChainTransactionService ruleChainTransactionService;
250 251
  252 + @Lazy
  253 + @Autowired
  254 + @Getter
  255 + private EdgeService edgeService;
  256 +
251 @Value("${cluster.partition_id}") 257 @Value("${cluster.partition_id}")
252 @Getter 258 @Getter
253 private long queuePartitionId; 259 private long queuePartitionId;
@@ -60,6 +60,7 @@ import org.thingsboard.server.dao.cassandra.CassandraCluster; @@ -60,6 +60,7 @@ import org.thingsboard.server.dao.cassandra.CassandraCluster;
60 import org.thingsboard.server.dao.customer.CustomerService; 60 import org.thingsboard.server.dao.customer.CustomerService;
61 import org.thingsboard.server.dao.dashboard.DashboardService; 61 import org.thingsboard.server.dao.dashboard.DashboardService;
62 import org.thingsboard.server.dao.device.DeviceService; 62 import org.thingsboard.server.dao.device.DeviceService;
  63 +import org.thingsboard.server.dao.edge.EdgeService;
63 import org.thingsboard.server.dao.entityview.EntityViewService; 64 import org.thingsboard.server.dao.entityview.EntityViewService;
64 import org.thingsboard.server.dao.nosql.CassandraStatementTask; 65 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
65 import org.thingsboard.server.dao.relation.RelationService; 66 import org.thingsboard.server.dao.relation.RelationService;
@@ -185,15 +186,26 @@ class DefaultTbContext implements TbContext { @@ -185,15 +186,26 @@ class DefaultTbContext implements TbContext {
185 } 186 }
186 187
187 public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) { 188 public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) {
  189 + return alarmMsg(alarm, ruleNodeId, DataConstants.ENTITY_CREATED);
  190 + }
  191 +
  192 + public TbMsg alarmUpdatedMsg(Alarm alarm, RuleNodeId ruleNodeId) {
  193 + return alarmMsg(alarm, ruleNodeId, DataConstants.ENTITY_UPDATED);
  194 + }
  195 +
  196 + public TbMsg alarmClearedMsg(Alarm alarm, RuleNodeId ruleNodeId) {
  197 + return alarmMsg(alarm, ruleNodeId, DataConstants.ALARM_CLEAR);
  198 + }
  199 +
  200 + private TbMsg alarmMsg(Alarm alarm, RuleNodeId ruleNodeId, String type) {
188 try { 201 try {
189 ObjectNode entityNode = mapper.valueToTree(alarm); 202 ObjectNode entityNode = mapper.valueToTree(alarm);
190 - return new TbMsg(UUIDs.timeBased(), DataConstants.ENTITY_CREATED, alarm.getId(), getActionMetaData(ruleNodeId), mapper.writeValueAsString(entityNode), null, null, 0L); 203 + return new TbMsg(UUIDs.timeBased(), type, alarm.getId(), getActionMetaData(ruleNodeId), mapper.writeValueAsString(entityNode), null, null, 0L);
191 } catch (JsonProcessingException | IllegalArgumentException e) { 204 } catch (JsonProcessingException | IllegalArgumentException e) {
192 - throw new RuntimeException("Failed to process alarm created msg: " + e); 205 + throw new RuntimeException("Failed to process alarm created, updated or cleared msg: " + e);
193 } 206 }
194 } 207 }
195 208
196 -  
197 @Override 209 @Override
198 public RuleNodeId getSelfId() { 210 public RuleNodeId getSelfId() {
199 return nodeCtx.getSelf().getId(); 211 return nodeCtx.getSelf().getId();
@@ -326,6 +338,11 @@ class DefaultTbContext implements TbContext { @@ -326,6 +338,11 @@ class DefaultTbContext implements TbContext {
326 } 338 }
327 339
328 @Override 340 @Override
  341 + public EdgeService getEdgeService() {
  342 + return mainCtx.getEdgeService();
  343 + }
  344 +
  345 + @Override
329 public EventLoopGroup getSharedEventLoop() { 346 public EventLoopGroup getSharedEventLoop() {
330 return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup(); 347 return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();
331 } 348 }
@@ -24,10 +24,12 @@ import com.datastax.driver.core.utils.UUIDs; @@ -24,10 +24,12 @@ import com.datastax.driver.core.utils.UUIDs;
24 import java.util.Optional; 24 import java.util.Optional;
25 25
26 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
  27 +import com.google.common.util.concurrent.FutureCallback;
27 import org.thingsboard.server.actors.ActorSystemContext; 28 import org.thingsboard.server.actors.ActorSystemContext;
28 import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; 29 import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
29 import org.thingsboard.server.actors.service.DefaultActorService; 30 import org.thingsboard.server.actors.service.DefaultActorService;
30 import org.thingsboard.server.actors.shared.ComponentMsgProcessor; 31 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
  32 +import org.thingsboard.server.common.data.DataConstants;
31 import org.thingsboard.server.common.data.EntityType; 33 import org.thingsboard.server.common.data.EntityType;
32 import org.thingsboard.server.common.data.id.EntityId; 34 import org.thingsboard.server.common.data.id.EntityId;
33 import org.thingsboard.server.common.data.id.RuleChainId; 35 import org.thingsboard.server.common.data.id.RuleChainId;
@@ -37,14 +39,17 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; @@ -37,14 +39,17 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
37 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; 39 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
38 import org.thingsboard.server.common.data.relation.EntityRelation; 40 import org.thingsboard.server.common.data.relation.EntityRelation;
39 import org.thingsboard.server.common.data.rule.RuleChain; 41 import org.thingsboard.server.common.data.rule.RuleChain;
  42 +import org.thingsboard.server.common.data.rule.RuleChainType;
40 import org.thingsboard.server.common.data.rule.RuleNode; 43 import org.thingsboard.server.common.data.rule.RuleNode;
41 import org.thingsboard.server.common.msg.TbMsg; 44 import org.thingsboard.server.common.msg.TbMsg;
42 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; 45 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
43 import org.thingsboard.server.common.msg.cluster.ServerAddress; 46 import org.thingsboard.server.common.msg.cluster.ServerAddress;
44 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; 47 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
45 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; 48 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  49 +import org.thingsboard.server.dao.edge.EdgeService;
46 import org.thingsboard.server.dao.rule.RuleChainService; 50 import org.thingsboard.server.dao.rule.RuleChainService;
47 51
  52 +import javax.annotation.Nullable;
48 import java.util.ArrayList; 53 import java.util.ArrayList;
49 import java.util.Collections; 54 import java.util.Collections;
50 import java.util.HashMap; 55 import java.util.HashMap;
@@ -65,6 +70,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh @@ -65,6 +70,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
65 private final Map<RuleNodeId, RuleNodeCtx> nodeActors; 70 private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
66 private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes; 71 private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes;
67 private final RuleChainService service; 72 private final RuleChainService service;
  73 + private final EdgeService edgeService;
68 74
69 private RuleNodeId firstId; 75 private RuleNodeId firstId;
70 private RuleNodeCtx firstNode; 76 private RuleNodeCtx firstNode;
@@ -79,6 +85,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh @@ -79,6 +85,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
79 this.nodeActors = new HashMap<>(); 85 this.nodeActors = new HashMap<>();
80 this.nodeRoutes = new HashMap<>(); 86 this.nodeRoutes = new HashMap<>();
81 this.service = systemContext.getRuleChainService(); 87 this.service = systemContext.getRuleChainService();
  88 + this.edgeService = systemContext.getEdgeService();
82 this.ruleChainName = ruleChainId.toString(); 89 this.ruleChainName = ruleChainId.toString();
83 } 90 }
84 91
@@ -92,17 +99,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh @@ -92,17 +99,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
92 if (!started) { 99 if (!started) {
93 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId); 100 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
94 if (ruleChain != null) { 101 if (ruleChain != null) {
95 - ruleChainName = ruleChain.getName();  
96 - List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);  
97 - log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());  
98 - // Creating and starting the actors;  
99 - for (RuleNode ruleNode : ruleNodeList) {  
100 - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);  
101 - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);  
102 - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); 102 + if (ruleChain.getType().equals(RuleChainType.SYSTEM)) {
  103 + ruleChainName = ruleChain.getName();
  104 + List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
  105 + log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
  106 + // Creating and starting the actors;
  107 + for (RuleNode ruleNode : ruleNodeList) {
  108 + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
  109 + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
  110 + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  111 + }
  112 + initRoutes(ruleChain, ruleNodeList);
  113 + started = true;
103 } 114 }
104 - initRoutes(ruleChain, ruleNodeList);  
105 - started = true;  
106 } 115 }
107 } else { 116 } else {
108 onUpdate(context); 117 onUpdate(context);
@@ -113,31 +122,36 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh @@ -113,31 +122,36 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
113 public void onUpdate(ActorContext context) { 122 public void onUpdate(ActorContext context) {
114 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId); 123 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
115 if (ruleChain != null) { 124 if (ruleChain != null) {
116 - ruleChainName = ruleChain.getName();  
117 - List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);  
118 - log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());  
119 - for (RuleNode ruleNode : ruleNodeList) {  
120 - RuleNodeCtx existing = nodeActors.get(ruleNode.getId());  
121 - if (existing == null) {  
122 - log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);  
123 - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);  
124 - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));  
125 - } else {  
126 - log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);  
127 - existing.setSelf(ruleNode);  
128 - existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); 125 + if (ruleChain.getType().equals(RuleChainType.SYSTEM)) {
  126 + ruleChainName = ruleChain.getName();
  127 + List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
  128 + log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
  129 + for (RuleNode ruleNode : ruleNodeList) {
  130 + RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
  131 + if (existing == null) {
  132 + log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
  133 + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
  134 + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  135 + } else {
  136 + log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
  137 + existing.setSelf(ruleNode);
  138 + existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
  139 + }
129 } 140 }
130 - }  
131 141
132 - Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());  
133 - List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());  
134 - removedRules.forEach(ruleNodeId -> {  
135 - log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);  
136 - RuleNodeCtx removed = nodeActors.remove(ruleNodeId);  
137 - removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);  
138 - }); 142 + Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
  143 + List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
  144 + removedRules.forEach(ruleNodeId -> {
  145 + log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
  146 + RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
  147 + removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
  148 + });
139 149
140 - initRoutes(ruleChain, ruleNodeList); 150 + initRoutes(ruleChain, ruleNodeList);
  151 +
  152 + } else if (ruleChain.getType().equals(RuleChainType.EDGE)){
  153 + stop(context);
  154 + }
141 } 155 }
142 } 156 }
143 157
@@ -326,6 +340,30 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh @@ -326,6 +340,30 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
326 if (nodeCtx != null) { 340 if (nodeCtx != null) {
327 nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType), self); 341 nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType), self);
328 } 342 }
  343 + pushUpdatesToEdges(msg);
  344 + }
  345 +
  346 + private void pushUpdatesToEdges(TbMsg msg) {
  347 + switch (msg.getType()) {
  348 + case DataConstants.ENTITY_CREATED:
  349 + case DataConstants.ENTITY_UPDATED:
  350 + case DataConstants.ENTITY_DELETED:
  351 + case DataConstants.ENTITY_ASSIGNED_TO_EDGE:
  352 + case DataConstants.ENTITY_UNASSIGNED_FROM_EDGE:
  353 + case DataConstants.ALARM_ACK:
  354 + case DataConstants.ALARM_CLEAR:
  355 + edgeService.pushEventToEdge(tenantId, msg, new FutureCallback<Void>() {
  356 + @Override
  357 + public void onSuccess(@Nullable Void aVoid) {
  358 + log.debug("Event saved successfully!");
  359 + }
  360 +
  361 + @Override
  362 + public void onFailure(Throwable t) {
  363 + log.debug("Failure during event save", t);
  364 + }
  365 + });
  366 + }
329 } 367 }
330 368
331 private TbMsg enrichWithRuleChainId(TbMsg tbMsg) { 369 private TbMsg enrichWithRuleChainId(TbMsg tbMsg) {
@@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.id.RuleChainId;
39 import org.thingsboard.server.common.data.id.TenantId; 39 import org.thingsboard.server.common.data.id.TenantId;
40 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 40 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
41 import org.thingsboard.server.common.data.rule.RuleChain; 41 import org.thingsboard.server.common.data.rule.RuleChain;
  42 +import org.thingsboard.server.common.data.rule.RuleChainType;
42 import org.thingsboard.server.common.msg.TbActorMsg; 43 import org.thingsboard.server.common.msg.TbActorMsg;
43 import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; 44 import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
44 import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg; 45 import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
@@ -139,11 +140,18 @@ public class TenantActor extends RuleChainManagerActor { @@ -139,11 +140,18 @@ public class TenantActor extends RuleChainManagerActor {
139 } 140 }
140 141
141 private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { 142 private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
  143 + RuleChain ruleChain = null;
  144 + if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
  145 + ruleChain = systemContext.getRuleChainService().findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
  146 + if (ruleChain !=null && !RuleChainType.SYSTEM.equals(ruleChain.getType())) {
  147 + log.debug("[{}] Non SYSTEM rule chains are ignored and not started. Current rule chain type [{}]", tenantId, ruleChain.getType());
  148 + return;
  149 + }
  150 + }
  151 +
142 ActorRef target = getEntityActorRef(msg.getEntityId()); 152 ActorRef target = getEntityActorRef(msg.getEntityId());
143 if (target != null) { 153 if (target != null) {
144 - if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {  
145 - RuleChain ruleChain = systemContext.getRuleChainService().  
146 - findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId())); 154 + if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN && ruleChain != null) {
147 ruleChainManager.visit(ruleChain, target); 155 ruleChainManager.visit(ruleChain, target);
148 } 156 }
149 target.tell(msg, ActorRef.noSender()); 157 target.tell(msg, ActorRef.noSender());
@@ -33,10 +33,12 @@ import org.thingsboard.server.common.data.asset.Asset; @@ -33,10 +33,12 @@ import org.thingsboard.server.common.data.asset.Asset;
33 import org.thingsboard.server.common.data.asset.AssetInfo; 33 import org.thingsboard.server.common.data.asset.AssetInfo;
34 import org.thingsboard.server.common.data.asset.AssetSearchQuery; 34 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
35 import org.thingsboard.server.common.data.audit.ActionType; 35 import org.thingsboard.server.common.data.audit.ActionType;
  36 +import org.thingsboard.server.common.data.edge.Edge;
36 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; 37 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
37 import org.thingsboard.server.common.data.exception.ThingsboardException; 38 import org.thingsboard.server.common.data.exception.ThingsboardException;
38 import org.thingsboard.server.common.data.id.AssetId; 39 import org.thingsboard.server.common.data.id.AssetId;
39 import org.thingsboard.server.common.data.id.CustomerId; 40 import org.thingsboard.server.common.data.id.CustomerId;
  41 +import org.thingsboard.server.common.data.id.EdgeId;
40 import org.thingsboard.server.common.data.id.TenantId; 42 import org.thingsboard.server.common.data.id.TenantId;
41 import org.thingsboard.server.common.data.page.PageData; 43 import org.thingsboard.server.common.data.page.PageData;
42 import org.thingsboard.server.common.data.page.PageLink; 44 import org.thingsboard.server.common.data.page.PageLink;
@@ -51,6 +53,8 @@ import java.util.ArrayList; @@ -51,6 +53,8 @@ import java.util.ArrayList;
51 import java.util.List; 53 import java.util.List;
52 import java.util.stream.Collectors; 54 import java.util.stream.Collectors;
53 55
  56 +import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
  57 +
54 @RestController 58 @RestController
55 @RequestMapping("/api") 59 @RequestMapping("/api")
56 public class AssetController extends BaseController { 60 public class AssetController extends BaseController {
@@ -396,4 +400,93 @@ public class AssetController extends BaseController { @@ -396,4 +400,93 @@ public class AssetController extends BaseController {
396 throw handleException(e); 400 throw handleException(e);
397 } 401 }
398 } 402 }
  403 +
  404 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  405 + @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
  406 + @ResponseBody
  407 + public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId,
  408 + @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
  409 + checkParameter(EDGE_ID, strEdgeId);
  410 + checkParameter(ASSET_ID, strAssetId);
  411 + try {
  412 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  413 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  414 +
  415 + AssetId assetId = new AssetId(toUUID(strAssetId));
  416 + checkAssetId(assetId, Operation.ASSIGN_TO_EDGE);
  417 +
  418 + Asset savedAsset = checkNotNull(assetService.assignAssetToEdge(getTenantId(), assetId, edgeId));
  419 +
  420 + logEntityAction(assetId, savedAsset,
  421 + savedAsset.getCustomerId(),
  422 + ActionType.ASSIGNED_TO_EDGE, null, strAssetId, strEdgeId, edge.getName());
  423 +
  424 + return savedAsset;
  425 + } catch (Exception e) {
  426 +
  427 + logEntityAction(emptyId(EntityType.ASSET), null,
  428 + null,
  429 + ActionType.ASSIGNED_TO_EDGE, e, strAssetId, strEdgeId);
  430 +
  431 + throw handleException(e);
  432 + }
  433 + }
  434 +
  435 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  436 + @RequestMapping(value = "/edge/asset/{assetId}", method = RequestMethod.DELETE)
  437 + @ResponseBody
  438 + public Asset unassignAssetFromEdge(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
  439 + checkParameter(ASSET_ID, strAssetId);
  440 + try {
  441 + AssetId assetId = new AssetId(toUUID(strAssetId));
  442 + Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_EDGE);
  443 + if (asset.getEdgeId() == null || asset.getEdgeId().getId().equals(ModelConstants.NULL_UUID)) {
  444 + throw new IncorrectParameterException("Asset isn't assigned to any edge!");
  445 + }
  446 +
  447 + Edge edge = checkEdgeId(asset.getEdgeId(), Operation.READ);
  448 +
  449 + Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(getTenantId(), assetId));
  450 +
  451 + logEntityAction(assetId, asset,
  452 + asset.getCustomerId(),
  453 + ActionType.UNASSIGNED_FROM_EDGE, null, strAssetId, edge.getId().toString(), edge.getName());
  454 +
  455 + return savedAsset;
  456 + } catch (Exception e) {
  457 +
  458 + logEntityAction(emptyId(EntityType.ASSET), null,
  459 + null,
  460 + ActionType.UNASSIGNED_FROM_EDGE, e, strAssetId);
  461 +
  462 + throw handleException(e);
  463 + }
  464 + }
  465 +
  466 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  467 + @RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
  468 + @ResponseBody
  469 + public PageData<Asset> getEdgeAssets(
  470 + @PathVariable("edgeId") String strEdgeId,
  471 + @RequestParam int pageSize,
  472 + @RequestParam int page,
  473 + @RequestParam(required = false) String type,
  474 + @RequestParam(required = false) String textSearch,
  475 + @RequestParam(required = false) String sortProperty,
  476 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  477 + checkParameter("edgeId", strEdgeId);
  478 + try {
  479 + TenantId tenantId = getCurrentUser().getTenantId();
  480 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  481 + checkEdgeId(edgeId, Operation.READ);
  482 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  483 + if (type != null && type.trim().length()>0) {
  484 + return checkNotNull(assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
  485 + } else {
  486 + return checkNotNull(assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
  487 + }
  488 + } catch (Exception e) {
  489 + throw handleException(e);
  490 + }
  491 + }
399 } 492 }
@@ -35,12 +35,14 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; @@ -35,12 +35,14 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
35 import org.thingsboard.server.common.data.asset.Asset; 35 import org.thingsboard.server.common.data.asset.Asset;
36 import org.thingsboard.server.common.data.asset.AssetInfo; 36 import org.thingsboard.server.common.data.asset.AssetInfo;
37 import org.thingsboard.server.common.data.audit.ActionType; 37 import org.thingsboard.server.common.data.audit.ActionType;
  38 +import org.thingsboard.server.common.data.edge.Edge;
38 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; 39 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
39 import org.thingsboard.server.common.data.exception.ThingsboardException; 40 import org.thingsboard.server.common.data.exception.ThingsboardException;
40 import org.thingsboard.server.common.data.id.AssetId; 41 import org.thingsboard.server.common.data.id.AssetId;
41 import org.thingsboard.server.common.data.id.CustomerId; 42 import org.thingsboard.server.common.data.id.CustomerId;
42 import org.thingsboard.server.common.data.id.DashboardId; 43 import org.thingsboard.server.common.data.id.DashboardId;
43 import org.thingsboard.server.common.data.id.DeviceId; 44 import org.thingsboard.server.common.data.id.DeviceId;
  45 +import org.thingsboard.server.common.data.id.EdgeId;
44 import org.thingsboard.server.common.data.id.EntityId; 46 import org.thingsboard.server.common.data.id.EntityId;
45 import org.thingsboard.server.common.data.id.EntityIdFactory; 47 import org.thingsboard.server.common.data.id.EntityIdFactory;
46 import org.thingsboard.server.common.data.id.EntityViewId; 48 import org.thingsboard.server.common.data.id.EntityViewId;
@@ -75,6 +77,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService; @@ -75,6 +77,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
75 import org.thingsboard.server.dao.device.ClaimDevicesService; 77 import org.thingsboard.server.dao.device.ClaimDevicesService;
76 import org.thingsboard.server.dao.device.DeviceCredentialsService; 78 import org.thingsboard.server.dao.device.DeviceCredentialsService;
77 import org.thingsboard.server.dao.device.DeviceService; 79 import org.thingsboard.server.dao.device.DeviceService;
  80 +import org.thingsboard.server.dao.edge.EdgeService;
78 import org.thingsboard.server.dao.entityview.EntityViewService; 81 import org.thingsboard.server.dao.entityview.EntityViewService;
79 import org.thingsboard.server.dao.exception.DataValidationException; 82 import org.thingsboard.server.dao.exception.DataValidationException;
80 import org.thingsboard.server.dao.exception.IncorrectParameterException; 83 import org.thingsboard.server.dao.exception.IncorrectParameterException;
@@ -178,6 +181,9 @@ public abstract class BaseController { @@ -178,6 +181,9 @@ public abstract class BaseController {
178 @Autowired 181 @Autowired
179 protected ClaimDevicesService claimDevicesService; 182 protected ClaimDevicesService claimDevicesService;
180 183
  184 + @Autowired
  185 + protected EdgeService edgeService;
  186 +
181 @Value("${server.log_controller_error_stack_trace}") 187 @Value("${server.log_controller_error_stack_trace}")
182 @Getter 188 @Getter
183 private boolean logControllerErrorStackTrace; 189 private boolean logControllerErrorStackTrace;
@@ -353,6 +359,9 @@ public abstract class BaseController { @@ -353,6 +359,9 @@ public abstract class BaseController {
353 case ENTITY_VIEW: 359 case ENTITY_VIEW:
354 checkEntityViewId(new EntityViewId(entityId.getId()), operation); 360 checkEntityViewId(new EntityViewId(entityId.getId()), operation);
355 return; 361 return;
  362 + case EDGE:
  363 + checkEdgeId(new EdgeId(entityId.getId()), operation);
  364 + return;
356 default: 365 default:
357 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); 366 throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
358 } 367 }
@@ -548,6 +557,17 @@ public abstract class BaseController { @@ -548,6 +557,17 @@ public abstract class BaseController {
548 return ruleNode; 557 return ruleNode;
549 } 558 }
550 559
  560 + Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException {
  561 + try {
  562 + validateId(edgeId, "Incorrect edgeId " + edgeId);
  563 + Edge edge = edgeService.findEdgeById(getTenantId(), edgeId);
  564 + checkNotNull(edge);
  565 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
  566 + return edge;
  567 + } catch (Exception e) {
  568 + throw handleException(e, false);
  569 + }
  570 + }
551 571
552 protected String constructBaseUrl(HttpServletRequest request) { 572 protected String constructBaseUrl(HttpServletRequest request) {
553 String scheme = request.getScheme(); 573 String scheme = request.getScheme();
@@ -637,6 +657,12 @@ public abstract class BaseController { @@ -637,6 +657,12 @@ public abstract class BaseController {
637 case ALARM_CLEAR: 657 case ALARM_CLEAR:
638 msgType = DataConstants.ALARM_CLEAR; 658 msgType = DataConstants.ALARM_CLEAR;
639 break; 659 break;
  660 + case ASSIGNED_TO_EDGE:
  661 + msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
  662 + break;
  663 + case UNASSIGNED_FROM_EDGE:
  664 + msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE;
  665 + break;
640 } 666 }
641 if (!StringUtils.isEmpty(msgType)) { 667 if (!StringUtils.isEmpty(msgType)) {
642 try { 668 try {
@@ -656,6 +682,12 @@ public abstract class BaseController { @@ -656,6 +682,12 @@ public abstract class BaseController {
656 String strCustomerName = extractParameter(String.class, 2, additionalInfo); 682 String strCustomerName = extractParameter(String.class, 2, additionalInfo);
657 metaData.putValue("unassignedCustomerId", strCustomerId); 683 metaData.putValue("unassignedCustomerId", strCustomerId);
658 metaData.putValue("unassignedCustomerName", strCustomerName); 684 metaData.putValue("unassignedCustomerName", strCustomerName);
  685 + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) {
  686 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  687 + metaData.putValue("assignedEdgeId", strEdgeId);
  688 + } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {
  689 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  690 + metaData.putValue("unassignedEdgeId", strEdgeId);
659 } 691 }
660 ObjectNode entityNode; 692 ObjectNode entityNode;
661 if (entity != null) { 693 if (entity != null) {
@@ -32,10 +32,13 @@ import org.thingsboard.server.common.data.Dashboard; @@ -32,10 +32,13 @@ import org.thingsboard.server.common.data.Dashboard;
32 import org.thingsboard.server.common.data.DashboardInfo; 32 import org.thingsboard.server.common.data.DashboardInfo;
33 import org.thingsboard.server.common.data.EntityType; 33 import org.thingsboard.server.common.data.EntityType;
34 import org.thingsboard.server.common.data.ShortCustomerInfo; 34 import org.thingsboard.server.common.data.ShortCustomerInfo;
  35 +import org.thingsboard.server.common.data.ShortEdgeInfo;
35 import org.thingsboard.server.common.data.audit.ActionType; 36 import org.thingsboard.server.common.data.audit.ActionType;
  37 +import org.thingsboard.server.common.data.edge.Edge;
36 import org.thingsboard.server.common.data.exception.ThingsboardException; 38 import org.thingsboard.server.common.data.exception.ThingsboardException;
37 import org.thingsboard.server.common.data.id.CustomerId; 39 import org.thingsboard.server.common.data.id.CustomerId;
38 import org.thingsboard.server.common.data.id.DashboardId; 40 import org.thingsboard.server.common.data.id.DashboardId;
  41 +import org.thingsboard.server.common.data.id.EdgeId;
39 import org.thingsboard.server.common.data.id.TenantId; 42 import org.thingsboard.server.common.data.id.TenantId;
40 import org.thingsboard.server.common.data.page.PageData; 43 import org.thingsboard.server.common.data.page.PageData;
41 import org.thingsboard.server.common.data.page.PageLink; 44 import org.thingsboard.server.common.data.page.PageLink;
@@ -474,4 +477,241 @@ public class DashboardController extends BaseController { @@ -474,4 +477,241 @@ public class DashboardController extends BaseController {
474 throw handleException(e); 477 throw handleException(e);
475 } 478 }
476 } 479 }
  480 +
  481 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  482 + @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST)
  483 + @ResponseBody
  484 + public Dashboard assignDashboardToEdge(@PathVariable("edgeId") String strEdgeId,
  485 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  486 + checkParameter("edgeId", strEdgeId);
  487 + checkParameter(DASHBOARD_ID, strDashboardId);
  488 + try {
  489 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  490 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  491 +
  492 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  493 + checkDashboardId(dashboardId, Operation.ASSIGN_TO_EDGE);
  494 +
  495 + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  496 +
  497 + logEntityAction(dashboardId, savedDashboard,
  498 + null,
  499 + ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, strEdgeId, edge.getName());
  500 +
  501 +
  502 + return savedDashboard;
  503 + } catch (Exception e) {
  504 +
  505 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  506 + null,
  507 + ActionType.ASSIGNED_TO_EDGE, e, strDashboardId, strEdgeId);
  508 +
  509 + throw handleException(e);
  510 + }
  511 + }
  512 +
  513 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  514 + @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
  515 + @ResponseBody
  516 + public Dashboard unassignDashboardFromEdge(@PathVariable("edgeId") String strEdgeId,
  517 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  518 + checkParameter("edgeId", strEdgeId);
  519 + checkParameter(DASHBOARD_ID, strDashboardId);
  520 + try {
  521 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  522 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  523 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  524 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_EDGE);
  525 +
  526 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  527 +
  528 + logEntityAction(dashboardId, dashboard,
  529 + null,
  530 + ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, edge.getId().toString(), edge.getName());
  531 +
  532 + return savedDashboard;
  533 + } catch (Exception e) {
  534 +
  535 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  536 + null,
  537 + ActionType.UNASSIGNED_FROM_EDGE, e, strDashboardId);
  538 +
  539 + throw handleException(e);
  540 + }
  541 + }
  542 +
  543 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  544 + @RequestMapping(value = "/dashboard/{dashboardId}/edges", method = RequestMethod.POST)
  545 + @ResponseBody
  546 + public Dashboard updateDashboardEdges(@PathVariable(DASHBOARD_ID) String strDashboardId,
  547 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  548 + checkParameter(DASHBOARD_ID, strDashboardId);
  549 + try {
  550 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  551 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_EDGE);
  552 +
  553 + Set<EdgeId> edgeIds = new HashSet<>();
  554 + if (strEdgeIds != null) {
  555 + for (String strEdgeId : strEdgeIds) {
  556 + edgeIds.add(new EdgeId(toUUID(strEdgeId)));
  557 + }
  558 + }
  559 +
  560 + Set<EdgeId> addedEdgeIds = new HashSet<>();
  561 + Set<EdgeId> removedEdgeIds = new HashSet<>();
  562 + for (EdgeId edgeId : edgeIds) {
  563 + if (!dashboard.isAssignedToEdge(edgeId)) {
  564 + addedEdgeIds.add(edgeId);
  565 + }
  566 + }
  567 +
  568 + Set<ShortEdgeInfo> assignedEdges = dashboard.getAssignedEdges();
  569 + if (assignedEdges != null) {
  570 + for (ShortEdgeInfo edgeInfo : assignedEdges) {
  571 + if (!edgeIds.contains(edgeInfo.getEdgeId())) {
  572 + removedEdgeIds.add(edgeInfo.getEdgeId());
  573 + }
  574 + }
  575 + }
  576 +
  577 + if (addedEdgeIds.isEmpty() && removedEdgeIds.isEmpty()) {
  578 + return dashboard;
  579 + } else {
  580 + Dashboard savedDashboard = null;
  581 + for (EdgeId edgeId : addedEdgeIds) {
  582 + savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  583 + ShortEdgeInfo edgeInfo = savedDashboard.getAssignedEdgeInfo(edgeId);
  584 + logEntityAction(dashboardId, savedDashboard,
  585 + null,
  586 + ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  587 + }
  588 + for (EdgeId edgeId : removedEdgeIds) {
  589 + ShortEdgeInfo edgeInfo = dashboard.getAssignedEdgeInfo(edgeId);
  590 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  591 + logEntityAction(dashboardId, dashboard,
  592 + null,
  593 + ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  594 +
  595 + }
  596 + return savedDashboard;
  597 + }
  598 + } catch (Exception e) {
  599 +
  600 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  601 + null,
  602 + ActionType.ASSIGNED_TO_EDGE, e, strDashboardId);
  603 +
  604 + throw handleException(e);
  605 + }
  606 + }
  607 +
  608 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  609 + @RequestMapping(value = "/dashboard/{dashboardId}/edges/add", method = RequestMethod.POST)
  610 + @ResponseBody
  611 + public Dashboard addDashboardEdges(@PathVariable(DASHBOARD_ID) String strDashboardId,
  612 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  613 + checkParameter(DASHBOARD_ID, strDashboardId);
  614 + try {
  615 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  616 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.ASSIGN_TO_EDGE);
  617 +
  618 + Set<EdgeId> edgeIds = new HashSet<>();
  619 + if (strEdgeIds != null) {
  620 + for (String strEdgeId : strEdgeIds) {
  621 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  622 + if (!dashboard.isAssignedToEdge(edgeId)) {
  623 + edgeIds.add(edgeId);
  624 + }
  625 + }
  626 + }
  627 +
  628 + if (edgeIds.isEmpty()) {
  629 + return dashboard;
  630 + } else {
  631 + Dashboard savedDashboard = null;
  632 + for (EdgeId edgeId : edgeIds) {
  633 + savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  634 + ShortEdgeInfo edgeInfo = savedDashboard.getAssignedEdgeInfo(edgeId);
  635 + logEntityAction(dashboardId, savedDashboard,
  636 + null,
  637 + ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  638 + }
  639 + return savedDashboard;
  640 + }
  641 + } catch (Exception e) {
  642 +
  643 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  644 + null,
  645 + ActionType.ASSIGNED_TO_EDGE, e, strDashboardId);
  646 +
  647 + throw handleException(e);
  648 + }
  649 + }
  650 +
  651 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  652 + @RequestMapping(value = "/dashboard/{dashboardId}/edges/remove", method = RequestMethod.POST)
  653 + @ResponseBody
  654 + public Dashboard removeDashboardEdges(@PathVariable(DASHBOARD_ID) String strDashboardId,
  655 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  656 + checkParameter(DASHBOARD_ID, strDashboardId);
  657 + try {
  658 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  659 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_EDGE);
  660 +
  661 + Set<EdgeId> edgeIds = new HashSet<>();
  662 + if (strEdgeIds != null) {
  663 + for (String strEdgeId : strEdgeIds) {
  664 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  665 + if (dashboard.isAssignedToEdge(edgeId)) {
  666 + edgeIds.add(edgeId);
  667 + }
  668 + }
  669 + }
  670 +
  671 + if (edgeIds.isEmpty()) {
  672 + return dashboard;
  673 + } else {
  674 + Dashboard savedDashboard = null;
  675 + for (EdgeId edgeId : edgeIds) {
  676 + ShortEdgeInfo edgeInfo = dashboard.getAssignedEdgeInfo(edgeId);
  677 + savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  678 + logEntityAction(dashboardId, dashboard,
  679 + null,
  680 + ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, edgeId.toString(), edgeInfo.getTitle());
  681 +
  682 + }
  683 + return savedDashboard;
  684 + }
  685 + } catch (Exception e) {
  686 +
  687 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  688 + null,
  689 + ActionType.UNASSIGNED_FROM_EDGE, e, strDashboardId);
  690 +
  691 + throw handleException(e);
  692 + }
  693 + }
  694 +
  695 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  696 + @RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
  697 + @ResponseBody
  698 + public PageData<DashboardInfo> getEdgeDashboards(
  699 + @PathVariable("edgeId") String strEdgeId,
  700 + @RequestParam int pageSize,
  701 + @RequestParam int page,
  702 + @RequestParam(required = false) String type,
  703 + @RequestParam(required = false) String textSearch,
  704 + @RequestParam(required = false) String sortProperty,
  705 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  706 + checkParameter("edgeId", strEdgeId);
  707 + try {
  708 + TenantId tenantId = getCurrentUser().getTenantId();
  709 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  710 + checkEdgeId(edgeId, Operation.READ);
  711 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  712 + return checkNotNull(dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
  713 + } catch (Exception e) {
  714 + throw handleException(e);
  715 + }
  716 + }
477 } 717 }
@@ -33,9 +33,11 @@ import org.springframework.web.context.request.async.DeferredResult; @@ -33,9 +33,11 @@ import org.springframework.web.context.request.async.DeferredResult;
33 import org.thingsboard.server.common.data.*; 33 import org.thingsboard.server.common.data.*;
34 import org.thingsboard.server.common.data.audit.ActionType; 34 import org.thingsboard.server.common.data.audit.ActionType;
35 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 35 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
  36 +import org.thingsboard.server.common.data.edge.Edge;
36 import org.thingsboard.server.common.data.exception.ThingsboardException; 37 import org.thingsboard.server.common.data.exception.ThingsboardException;
37 import org.thingsboard.server.common.data.id.CustomerId; 38 import org.thingsboard.server.common.data.id.CustomerId;
38 import org.thingsboard.server.common.data.id.DeviceId; 39 import org.thingsboard.server.common.data.id.DeviceId;
  40 +import org.thingsboard.server.common.data.id.EdgeId;
39 import org.thingsboard.server.common.data.id.TenantId; 41 import org.thingsboard.server.common.data.id.TenantId;
40 import org.thingsboard.server.common.data.page.PageData; 42 import org.thingsboard.server.common.data.page.PageData;
41 import org.thingsboard.server.common.data.page.PageLink; 43 import org.thingsboard.server.common.data.page.PageLink;
@@ -55,6 +57,8 @@ import java.util.ArrayList; @@ -55,6 +57,8 @@ import java.util.ArrayList;
55 import java.util.List; 57 import java.util.List;
56 import java.util.stream.Collectors; 58 import java.util.stream.Collectors;
57 59
  60 +import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
  61 +
58 @RestController 62 @RestController
59 @RequestMapping("/api") 63 @RequestMapping("/api")
60 public class DeviceController extends BaseController { 64 public class DeviceController extends BaseController {
@@ -541,4 +545,88 @@ public class DeviceController extends BaseController { @@ -541,4 +545,88 @@ public class DeviceController extends BaseController {
541 } 545 }
542 return DataConstants.DEFAULT_SECRET_KEY; 546 return DataConstants.DEFAULT_SECRET_KEY;
543 } 547 }
  548 +
  549 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  550 + @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
  551 + @ResponseBody
  552 + public Device assignDeviceToEdge(@PathVariable(EDGE_ID) String strEdgeId,
  553 + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
  554 + checkParameter(EDGE_ID, strEdgeId);
  555 + checkParameter(DEVICE_ID, strDeviceId);
  556 + try {
  557 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  558 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  559 +
  560 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  561 + checkDeviceId(deviceId, Operation.ASSIGN_TO_EDGE);
  562 +
  563 + Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
  564 +
  565 + logEntityAction(deviceId, savedDevice,
  566 + savedDevice.getCustomerId(),
  567 + ActionType.ASSIGNED_TO_EDGE, null, strDeviceId, strEdgeId, edge.getName());
  568 +
  569 + return savedDevice;
  570 + } catch (Exception e) {
  571 + logEntityAction(emptyId(EntityType.DEVICE), null,
  572 + null,
  573 + ActionType.ASSIGNED_TO_EDGE, e, strDeviceId, strEdgeId);
  574 + throw handleException(e);
  575 + }
  576 + }
  577 +
  578 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  579 + @RequestMapping(value = "/edge/device/{deviceId}", method = RequestMethod.DELETE)
  580 + @ResponseBody
  581 + public Device unassignDeviceFromEdge(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
  582 + checkParameter(DEVICE_ID, strDeviceId);
  583 + try {
  584 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  585 + Device device = checkDeviceId(deviceId, Operation.UNASSIGN_FROM_EDGE);
  586 + if (device.getEdgeId() == null || device.getEdgeId().getId().equals(ModelConstants.NULL_UUID)) {
  587 + throw new IncorrectParameterException("Device isn't assigned to any edge!");
  588 + }
  589 + Edge edge = checkEdgeId(device.getEdgeId(), Operation.READ);
  590 +
  591 + Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(getCurrentUser().getTenantId(), deviceId));
  592 +
  593 + logEntityAction(deviceId, device,
  594 + device.getCustomerId(),
  595 + ActionType.UNASSIGNED_FROM_EDGE, null, strDeviceId, edge.getId().toString(), edge.getName());
  596 +
  597 + return savedDevice;
  598 + } catch (Exception e) {
  599 + logEntityAction(emptyId(EntityType.DEVICE), null,
  600 + null,
  601 + ActionType.UNASSIGNED_FROM_EDGE, e, strDeviceId);
  602 + throw handleException(e);
  603 + }
  604 + }
  605 +
  606 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  607 + @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
  608 + @ResponseBody
  609 + public PageData<Device> getEdgeDevices(
  610 + @PathVariable("edgeId") String strEdgeId,
  611 + @RequestParam int pageSize,
  612 + @RequestParam int page,
  613 + @RequestParam(required = false) String type,
  614 + @RequestParam(required = false) String textSearch,
  615 + @RequestParam(required = false) String sortProperty,
  616 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  617 + checkParameter("edgeId", strEdgeId);
  618 + try {
  619 + TenantId tenantId = getCurrentUser().getTenantId();
  620 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  621 + checkEdgeId(edgeId, Operation.READ);
  622 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  623 + if (type != null && type.trim().length()>0) {
  624 + return checkNotNull(deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
  625 + } else {
  626 + return checkNotNull(deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
  627 + }
  628 + } catch (Exception e) {
  629 + throw handleException(e);
  630 + }
  631 + }
544 } 632 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.springframework.http.HttpStatus;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.PathVariable;
  22 +import org.springframework.web.bind.annotation.RequestBody;
  23 +import org.springframework.web.bind.annotation.RequestMapping;
  24 +import org.springframework.web.bind.annotation.RequestMethod;
  25 +import org.springframework.web.bind.annotation.RequestParam;
  26 +import org.springframework.web.bind.annotation.ResponseBody;
  27 +import org.springframework.web.bind.annotation.ResponseStatus;
  28 +import org.springframework.web.bind.annotation.RestController;
  29 +import org.thingsboard.server.common.data.Customer;
  30 +import org.thingsboard.server.common.data.EntitySubtype;
  31 +import org.thingsboard.server.common.data.EntityType;
  32 +import org.thingsboard.server.common.data.audit.ActionType;
  33 +import org.thingsboard.server.common.data.edge.Edge;
  34 +import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
  35 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  36 +import org.thingsboard.server.common.data.id.CustomerId;
  37 +import org.thingsboard.server.common.data.id.EdgeId;
  38 +import org.thingsboard.server.common.data.id.RuleChainId;
  39 +import org.thingsboard.server.common.data.id.TenantId;
  40 +import org.thingsboard.server.common.data.page.PageData;
  41 +import org.thingsboard.server.common.data.page.PageLink;
  42 +import org.thingsboard.server.common.data.rule.RuleChain;
  43 +import org.thingsboard.server.dao.exception.IncorrectParameterException;
  44 +import org.thingsboard.server.dao.model.ModelConstants;
  45 +import org.thingsboard.server.service.security.model.SecurityUser;
  46 +import org.thingsboard.server.service.security.permission.Operation;
  47 +import org.thingsboard.server.service.security.permission.Resource;
  48 +
  49 +import java.util.ArrayList;
  50 +import java.util.List;
  51 +import java.util.stream.Collectors;
  52 +
  53 +@RestController
  54 +@RequestMapping("/api")
  55 +public class EdgeController extends BaseController {
  56 +
  57 + public static final String EDGE_ID = "edgeId";
  58 +
  59 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  60 + @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET)
  61 + @ResponseBody
  62 + public Edge getEdgeById(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  63 + checkParameter(EDGE_ID, strEdgeId);
  64 + try {
  65 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  66 + return checkEdgeId(edgeId, Operation.READ);
  67 + } catch (Exception e) {
  68 + throw handleException(e);
  69 + }
  70 + }
  71 +
  72 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  73 + @RequestMapping(value = "/edge", method = RequestMethod.POST)
  74 + @ResponseBody
  75 + public Edge saveEdge(@RequestBody Edge edge) throws ThingsboardException {
  76 + try {
  77 + TenantId tenantId = getCurrentUser().getTenantId();
  78 + edge.setTenantId(tenantId);
  79 + boolean created = edge.getId() == null;
  80 +
  81 + Operation operation = created ? Operation.CREATE : Operation.WRITE;
  82 +
  83 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
  84 + edge.getId(), edge);
  85 +
  86 + Edge result = checkNotNull(edgeService.saveEdge(edge));
  87 +
  88 + if (created) {
  89 + RuleChain rootTenantRuleChain = ruleChainService.getRootTenantRuleChain(tenantId);
  90 + ruleChainService.assignRuleChainToEdge(tenantId, rootTenantRuleChain.getId(), result.getId());
  91 + edgeService.setRootRuleChain(tenantId, result, rootTenantRuleChain.getId());
  92 + }
  93 +
  94 + logEntityAction(result.getId(), result, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
  95 + return result;
  96 + } catch (Exception e) {
  97 + logEntityAction(emptyId(EntityType.EDGE), edge,
  98 + null, edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  99 + throw handleException(e);
  100 + }
  101 + }
  102 +
  103 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  104 + @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE)
  105 + @ResponseStatus(value = HttpStatus.OK)
  106 + public void deleteEdge(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  107 + checkParameter(EDGE_ID, strEdgeId);
  108 + try {
  109 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  110 + Edge edge = checkEdgeId(edgeId, Operation.DELETE);
  111 + edgeService.deleteEdge(getTenantId(), edgeId);
  112 +
  113 + logEntityAction(edgeId, edge,
  114 + null,
  115 + ActionType.DELETED, null, strEdgeId);
  116 +
  117 + } catch (Exception e) {
  118 +
  119 + logEntityAction(emptyId(EntityType.EDGE),
  120 + null,
  121 + null,
  122 + ActionType.DELETED, e, strEdgeId);
  123 +
  124 + throw handleException(e);
  125 + }
  126 + }
  127 +
  128 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  129 + @RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
  130 + @ResponseBody
  131 + public PageData<Edge> getEdges(@RequestParam int pageSize,
  132 + @RequestParam int page,
  133 + @RequestParam(required = false) String textSearch,
  134 + @RequestParam(required = false) String sortProperty,
  135 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  136 + try {
  137 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  138 + TenantId tenantId = getCurrentUser().getTenantId();
  139 + return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink));
  140 + } catch (Exception e) {
  141 + throw handleException(e);
  142 + }
  143 + }
  144 +
  145 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  146 + @RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST)
  147 + @ResponseBody
  148 + public Edge assignEdgeToCustomer(@PathVariable("customerId") String strCustomerId,
  149 + @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  150 + checkParameter("customerId", strCustomerId);
  151 + checkParameter(EDGE_ID, strEdgeId);
  152 + try {
  153 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  154 + Customer customer = checkCustomerId(customerId, Operation.READ);
  155 +
  156 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  157 + checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER);
  158 +
  159 + Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, customerId));
  160 +
  161 + logEntityAction(edgeId, savedEdge,
  162 + savedEdge.getCustomerId(),
  163 + ActionType.ASSIGNED_TO_CUSTOMER, null, strEdgeId, strCustomerId, customer.getName());
  164 +
  165 + return savedEdge;
  166 + } catch (Exception e) {
  167 + logEntityAction(emptyId(EntityType.EDGE), null,
  168 + null,
  169 + ActionType.ASSIGNED_TO_CUSTOMER, e, strEdgeId, strCustomerId);
  170 + throw handleException(e);
  171 + }
  172 + }
  173 +
  174 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  175 + @RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE)
  176 + @ResponseBody
  177 + public Edge unassignEdgeFromCustomer(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  178 + checkParameter(EDGE_ID, strEdgeId);
  179 + try {
  180 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  181 + Edge edge = checkEdgeId(edgeId, Operation.UNASSIGN_FROM_CUSTOMER);
  182 + if (edge.getCustomerId() == null || edge.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
  183 + throw new IncorrectParameterException("Edge isn't assigned to any customer!");
  184 + }
  185 + Customer customer = checkCustomerId(edge.getCustomerId(), Operation.READ);
  186 +
  187 + Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(getCurrentUser().getTenantId(), edgeId));
  188 +
  189 + logEntityAction(edgeId, edge,
  190 + edge.getCustomerId(),
  191 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEdgeId, customer.getId().toString(), customer.getName());
  192 +
  193 + return savedEdge;
  194 + } catch (Exception e) {
  195 + logEntityAction(emptyId(EntityType.EDGE), null,
  196 + null,
  197 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEdgeId);
  198 + throw handleException(e);
  199 + }
  200 + }
  201 +
  202 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  203 + @RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST)
  204 + @ResponseBody
  205 + public Edge assignEdgeToPublicCustomer(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  206 + checkParameter(EDGE_ID, strEdgeId);
  207 + try {
  208 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  209 + Edge edge = checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER);
  210 + Customer publicCustomer = customerService.findOrCreatePublicCustomer(edge.getTenantId());
  211 + Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, publicCustomer.getId()));
  212 +
  213 + logEntityAction(edgeId, savedEdge,
  214 + savedEdge.getCustomerId(),
  215 + ActionType.ASSIGNED_TO_CUSTOMER, null, strEdgeId, publicCustomer.getId().toString(), publicCustomer.getName());
  216 +
  217 + return savedEdge;
  218 + } catch (Exception e) {
  219 + logEntityAction(emptyId(EntityType.EDGE), null,
  220 + null,
  221 + ActionType.ASSIGNED_TO_CUSTOMER, e, strEdgeId);
  222 + throw handleException(e);
  223 + }
  224 + }
  225 +
  226 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  227 + @RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
  228 + @ResponseBody
  229 + public PageData<Edge> getTenantEdges(
  230 + @RequestParam int pageSize,
  231 + @RequestParam int page,
  232 + @RequestParam(required = false) String type,
  233 + @RequestParam(required = false) String textSearch,
  234 + @RequestParam(required = false) String sortProperty,
  235 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  236 + try {
  237 + TenantId tenantId = getCurrentUser().getTenantId();
  238 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  239 + if (type != null && type.trim().length() > 0) {
  240 + return checkNotNull(edgeService.findEdgesByTenantIdAndType(tenantId, type, pageLink));
  241 + } else {
  242 + return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink));
  243 + }
  244 + } catch (Exception e) {
  245 + throw handleException(e);
  246 + }
  247 + }
  248 +
  249 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  250 + @RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET)
  251 + @ResponseBody
  252 + public Edge getTenantEdge(
  253 + @RequestParam String edgeName) throws ThingsboardException {
  254 + try {
  255 + TenantId tenantId = getCurrentUser().getTenantId();
  256 + return checkNotNull(edgeService.findEdgeByTenantIdAndName(tenantId, edgeName));
  257 + } catch (Exception e) {
  258 + throw handleException(e);
  259 + }
  260 + }
  261 +
  262 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  263 + @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST)
  264 + @ResponseBody
  265 + public Edge setRootRuleChain(@PathVariable(EDGE_ID) String strEdgeId,
  266 + @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException {
  267 + checkParameter(EDGE_ID, strEdgeId);
  268 + checkParameter("ruleChainId", strRuleChainId);
  269 + try {
  270 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  271 + checkRuleChain(ruleChainId, Operation.WRITE);
  272 +
  273 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  274 + Edge edge = checkEdgeId(edgeId, Operation.WRITE);
  275 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.WRITE,
  276 + edge.getId(), edge);
  277 +
  278 + Edge updatedEdge = edgeService.setRootRuleChain(getTenantId(), edge, ruleChainId);
  279 +
  280 + logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null);
  281 +
  282 + return updatedEdge;
  283 + } catch (Exception e) {
  284 + logEntityAction(emptyId(EntityType.EDGE),
  285 + null,
  286 + null,
  287 + ActionType.UPDATED, e, strEdgeId);
  288 + throw handleException(e);
  289 + }
  290 + }
  291 +
  292 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  293 + @RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
  294 + @ResponseBody
  295 + public PageData<Edge> getCustomerEdges(
  296 + @PathVariable("customerId") String strCustomerId,
  297 + @RequestParam int pageSize,
  298 + @RequestParam int page,
  299 + @RequestParam(required = false) String type,
  300 + @RequestParam(required = false) String textSearch,
  301 + @RequestParam(required = false) String sortProperty,
  302 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  303 + checkParameter("customerId", strCustomerId);
  304 + try {
  305 + TenantId tenantId = getCurrentUser().getTenantId();
  306 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  307 + checkCustomerId(customerId, Operation.READ);
  308 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  309 + if (type != null && type.trim().length() > 0) {
  310 + return checkNotNull(edgeService.findEdgesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
  311 + } else {
  312 + return checkNotNull(edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink));
  313 + }
  314 + } catch (Exception e) {
  315 + throw handleException(e);
  316 + }
  317 + }
  318 +
  319 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  320 + @RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET)
  321 + @ResponseBody
  322 + public List<Edge> getEdgesByIds(
  323 + @RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException {
  324 + checkArrayParameter("edgeIds", strEdgeIds);
  325 + try {
  326 + SecurityUser user = getCurrentUser();
  327 + TenantId tenantId = user.getTenantId();
  328 + CustomerId customerId = user.getCustomerId();
  329 + List<EdgeId> edgeIds = new ArrayList<>();
  330 + for (String strEdgeId : strEdgeIds) {
  331 + edgeIds.add(new EdgeId(toUUID(strEdgeId)));
  332 + }
  333 + ListenableFuture<List<Edge>> edges;
  334 + if (customerId == null || customerId.isNullUid()) {
  335 + edges = edgeService.findEdgesByTenantIdAndIdsAsync(tenantId, edgeIds);
  336 + } else {
  337 + edges = edgeService.findEdgesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, edgeIds);
  338 + }
  339 + return checkNotNull(edges.get());
  340 + } catch (Exception e) {
  341 + throw handleException(e);
  342 + }
  343 + }
  344 +
  345 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  346 + @RequestMapping(value = "/edges", method = RequestMethod.POST)
  347 + @ResponseBody
  348 + public List<Edge> findByQuery(@RequestBody EdgeSearchQuery query) throws ThingsboardException {
  349 + checkNotNull(query);
  350 + checkNotNull(query.getParameters());
  351 + checkNotNull(query.getEdgeTypes());
  352 + checkEntityId(query.getParameters().getEntityId(), Operation.READ);
  353 + try {
  354 + List<Edge> edges = checkNotNull(edgeService.findEdgesByQuery(getCurrentUser().getTenantId(), query).get());
  355 + edges = edges.stream().filter(edge -> {
  356 + try {
  357 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.READ, edge.getId(), edge);
  358 + return true;
  359 + } catch (ThingsboardException e) {
  360 + return false;
  361 + }
  362 + }).collect(Collectors.toList());
  363 + return edges;
  364 + } catch (Exception e) {
  365 + throw handleException(e);
  366 + }
  367 + }
  368 +
  369 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  370 + @RequestMapping(value = "/edge/types", method = RequestMethod.GET)
  371 + @ResponseBody
  372 + public List<EntitySubtype> getEdgeTypes() throws ThingsboardException {
  373 + try {
  374 + SecurityUser user = getCurrentUser();
  375 + TenantId tenantId = user.getTenantId();
  376 + ListenableFuture<List<EntitySubtype>> edgeTypes = edgeService.findEdgeTypesByTenantId(tenantId);
  377 + return checkNotNull(edgeTypes.get());
  378 + } catch (Exception e) {
  379 + throw handleException(e);
  380 + }
  381 + }
  382 +
  383 +}
@@ -31,9 +31,11 @@ import org.springframework.web.bind.annotation.ResponseStatus; @@ -31,9 +31,11 @@ import org.springframework.web.bind.annotation.ResponseStatus;
31 import org.springframework.web.bind.annotation.RestController; 31 import org.springframework.web.bind.annotation.RestController;
32 import org.thingsboard.server.common.data.*; 32 import org.thingsboard.server.common.data.*;
33 import org.thingsboard.server.common.data.audit.ActionType; 33 import org.thingsboard.server.common.data.audit.ActionType;
  34 +import org.thingsboard.server.common.data.edge.Edge;
34 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; 35 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
35 import org.thingsboard.server.common.data.exception.ThingsboardException; 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
36 import org.thingsboard.server.common.data.id.CustomerId; 37 import org.thingsboard.server.common.data.id.CustomerId;
  38 +import org.thingsboard.server.common.data.id.EdgeId;
37 import org.thingsboard.server.common.data.id.EntityId; 39 import org.thingsboard.server.common.data.id.EntityId;
38 import org.thingsboard.server.common.data.id.EntityViewId; 40 import org.thingsboard.server.common.data.id.EntityViewId;
39 import org.thingsboard.server.common.data.id.TenantId; 41 import org.thingsboard.server.common.data.id.TenantId;
@@ -55,6 +57,7 @@ import java.util.concurrent.ExecutionException; @@ -55,6 +57,7 @@ import java.util.concurrent.ExecutionException;
55 import java.util.stream.Collectors; 57 import java.util.stream.Collectors;
56 58
57 import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID; 59 import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
  60 +import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
58 61
59 /** 62 /**
60 * Created by Victor Basanets on 8/28/2017. 63 * Created by Victor Basanets on 8/28/2017.
@@ -426,4 +429,84 @@ public class EntityViewController extends BaseController { @@ -426,4 +429,84 @@ public class EntityViewController extends BaseController {
426 throw handleException(e); 429 throw handleException(e);
427 } 430 }
428 } 431 }
  432 +
  433 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  434 + @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.POST)
  435 + @ResponseBody
  436 + public EntityView assignEntityViewToEdge(@PathVariable(EDGE_ID) String strEdgeId,
  437 + @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  438 + checkParameter(EDGE_ID, strEdgeId);
  439 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  440 + try {
  441 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  442 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  443 +
  444 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  445 + checkEntityViewId(entityViewId, Operation.ASSIGN_TO_EDGE);
  446 +
  447 + EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToEdge(getTenantId(), entityViewId, edgeId));
  448 + logEntityAction(entityViewId, savedEntityView,
  449 + savedEntityView.getCustomerId(),
  450 + ActionType.ASSIGNED_TO_EDGE, null, strEntityViewId, strEdgeId, edge.getName());
  451 + return savedEntityView;
  452 + } catch (Exception e) {
  453 + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
  454 + null,
  455 + ActionType.ASSIGNED_TO_EDGE, e, strEntityViewId, strEdgeId);
  456 + throw handleException(e);
  457 + }
  458 + }
  459 +
  460 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  461 + @RequestMapping(value = "/edge/entityView/{entityViewId}", method = RequestMethod.DELETE)
  462 + @ResponseBody
  463 + public EntityView unassignEntityViewFromEdge(@PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  464 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  465 + try {
  466 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  467 + EntityView entityView = checkEntityViewId(entityViewId, Operation.UNASSIGN_FROM_EDGE);
  468 + if (entityView.getEdgeId() == null || entityView.getEdgeId().getId().equals(ModelConstants.NULL_UUID)) {
  469 + throw new IncorrectParameterException("Entity View isn't assigned to any edge!");
  470 + }
  471 + Edge edge = checkEdgeId(entityView.getEdgeId(), Operation.READ);
  472 + EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromEdge(getTenantId(), entityViewId));
  473 + logEntityAction(entityViewId, entityView,
  474 + entityView.getCustomerId(),
  475 + ActionType.UNASSIGNED_FROM_EDGE, null, strEntityViewId, edge.getId().toString(), edge.getName());
  476 +
  477 + return savedEntityView;
  478 + } catch (Exception e) {
  479 + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
  480 + null,
  481 + ActionType.UNASSIGNED_FROM_EDGE, e, strEntityViewId);
  482 + throw handleException(e);
  483 + }
  484 + }
  485 +
  486 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  487 + @RequestMapping(value = "/edge/{edgeId}/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET)
  488 + @ResponseBody
  489 + public PageData<EntityView> getEdgeEntityViews(
  490 + @PathVariable("edgeId") String strEdgeId,
  491 + @RequestParam int pageSize,
  492 + @RequestParam int page,
  493 + @RequestParam(required = false) String type,
  494 + @RequestParam(required = false) String textSearch,
  495 + @RequestParam(required = false) String sortProperty,
  496 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  497 + checkParameter("edgeId", strEdgeId);
  498 + try {
  499 + TenantId tenantId = getCurrentUser().getTenantId();
  500 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  501 + checkEdgeId(edgeId, Operation.READ);
  502 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  503 + if (type != null && type.trim().length()>0) {
  504 + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink));
  505 + } else {
  506 + return checkNotNull(entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
  507 + }
  508 + } catch (Exception e) {
  509 + throw handleException(e);
  510 + }
  511 + }
429 } 512 }
@@ -40,16 +40,21 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits; @@ -40,16 +40,21 @@ import org.thingsboard.server.actors.tenant.DebugTbRateLimits;
40 import org.thingsboard.server.common.data.DataConstants; 40 import org.thingsboard.server.common.data.DataConstants;
41 import org.thingsboard.server.common.data.EntityType; 41 import org.thingsboard.server.common.data.EntityType;
42 import org.thingsboard.server.common.data.Event; 42 import org.thingsboard.server.common.data.Event;
  43 +import org.thingsboard.server.common.data.ShortEdgeInfo;
43 import org.thingsboard.server.common.data.audit.ActionType; 44 import org.thingsboard.server.common.data.audit.ActionType;
  45 +import org.thingsboard.server.common.data.edge.Edge;
44 import org.thingsboard.server.common.data.exception.ThingsboardException; 46 import org.thingsboard.server.common.data.exception.ThingsboardException;
  47 +import org.thingsboard.server.common.data.id.EdgeId;
45 import org.thingsboard.server.common.data.id.RuleChainId; 48 import org.thingsboard.server.common.data.id.RuleChainId;
46 import org.thingsboard.server.common.data.id.RuleNodeId; 49 import org.thingsboard.server.common.data.id.RuleNodeId;
47 import org.thingsboard.server.common.data.id.TenantId; 50 import org.thingsboard.server.common.data.id.TenantId;
48 import org.thingsboard.server.common.data.page.PageData; 51 import org.thingsboard.server.common.data.page.PageData;
49 import org.thingsboard.server.common.data.page.PageLink; 52 import org.thingsboard.server.common.data.page.PageLink;
  53 +import org.thingsboard.server.common.data.page.TimePageLink;
50 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 54 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
51 import org.thingsboard.server.common.data.rule.RuleChain; 55 import org.thingsboard.server.common.data.rule.RuleChain;
52 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 56 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  57 +import org.thingsboard.server.common.data.rule.RuleChainType;
53 import org.thingsboard.server.common.data.rule.RuleNode; 58 import org.thingsboard.server.common.data.rule.RuleNode;
54 import org.thingsboard.server.common.msg.TbMsg; 59 import org.thingsboard.server.common.msg.TbMsg;
55 import org.thingsboard.server.common.msg.TbMsgMetaData; 60 import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -59,6 +64,7 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; @@ -59,6 +64,7 @@ import org.thingsboard.server.service.script.RuleNodeJsScriptEngine;
59 import org.thingsboard.server.service.security.permission.Operation; 64 import org.thingsboard.server.service.security.permission.Operation;
60 import org.thingsboard.server.service.security.permission.Resource; 65 import org.thingsboard.server.service.security.permission.Resource;
61 66
  67 +import java.util.HashSet;
62 import java.util.List; 68 import java.util.List;
63 import java.util.Map; 69 import java.util.Map;
64 import java.util.Set; 70 import java.util.Set;
@@ -225,13 +231,19 @@ public class RuleChainController extends BaseController { @@ -225,13 +231,19 @@ public class RuleChainController extends BaseController {
225 public PageData<RuleChain> getRuleChains( 231 public PageData<RuleChain> getRuleChains(
226 @RequestParam int pageSize, 232 @RequestParam int pageSize,
227 @RequestParam int page, 233 @RequestParam int page,
  234 + @RequestParam(value = "type", required = false) String typeStr,
228 @RequestParam(required = false) String textSearch, 235 @RequestParam(required = false) String textSearch,
229 @RequestParam(required = false) String sortProperty, 236 @RequestParam(required = false) String sortProperty,
230 @RequestParam(required = false) String sortOrder) throws ThingsboardException { 237 @RequestParam(required = false) String sortOrder) throws ThingsboardException {
231 try { 238 try {
232 TenantId tenantId = getCurrentUser().getTenantId(); 239 TenantId tenantId = getCurrentUser().getTenantId();
233 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); 240 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
234 - return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink)); 241 + if (typeStr != null && typeStr.trim().length() > 0) {
  242 + RuleChainType type = RuleChainType.valueOf(typeStr);
  243 + return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink));
  244 + } else {
  245 + return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
  246 + }
235 } catch (Exception e) { 247 } catch (Exception e) {
236 throw handleException(e); 248 throw handleException(e);
237 } 249 }
@@ -372,4 +384,242 @@ public class RuleChainController extends BaseController { @@ -372,4 +384,242 @@ public class RuleChainController extends BaseController {
372 return objectMapper.writeValueAsString(msgData); 384 return objectMapper.writeValueAsString(msgData);
373 } 385 }
374 386
  387 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  388 + @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST)
  389 + @ResponseBody
  390 + public RuleChain assignRuleChainToEdge(@PathVariable("edgeId") String strEdgeId,
  391 + @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
  392 + checkParameter("edgeId", strEdgeId);
  393 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  394 + try {
  395 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  396 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  397 +
  398 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  399 + checkRuleChain(ruleChainId, Operation.ASSIGN_TO_EDGE);
  400 +
  401 + RuleChain savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId));
  402 +
  403 + logEntityAction(ruleChainId, savedRuleChain,
  404 + null,
  405 + ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, strEdgeId, edge.getName());
  406 +
  407 +
  408 + return savedRuleChain;
  409 + } catch (Exception e) {
  410 +
  411 + logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
  412 + null,
  413 + ActionType.ASSIGNED_TO_EDGE, e, strRuleChainId, strEdgeId);
  414 +
  415 + throw handleException(e);
  416 + }
  417 + }
  418 +
  419 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  420 + @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
  421 + @ResponseBody
  422 + public RuleChain unassignRuleChainFromEdge(@PathVariable("edgeId") String strEdgeId,
  423 + @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
  424 + checkParameter("edgeId", strEdgeId);
  425 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  426 + try {
  427 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  428 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  429 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  430 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.UNASSIGN_FROM_EDGE);
  431 +
  432 + RuleChain savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId));
  433 +
  434 + logEntityAction(ruleChainId, ruleChain,
  435 + null,
  436 + ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, edge.getId().toString(), edge.getName());
  437 +
  438 + return savedRuleChain;
  439 + } catch (Exception e) {
  440 +
  441 + logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
  442 + null,
  443 + ActionType.UNASSIGNED_FROM_EDGE, e, strRuleChainId);
  444 +
  445 + throw handleException(e);
  446 + }
  447 + }
  448 +
  449 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  450 + @RequestMapping(value = "/ruleChain/{ruleChainId}/edges", method = RequestMethod.POST)
  451 + @ResponseBody
  452 + public RuleChain updateRuleChainEdges(@PathVariable(RULE_CHAIN_ID) String strRuleChainId,
  453 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  454 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  455 + try {
  456 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  457 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.ASSIGN_TO_EDGE);
  458 +
  459 + Set<EdgeId> edgeIds = new HashSet<>();
  460 + if (strEdgeIds != null) {
  461 + for (String strEdgeId : strEdgeIds) {
  462 + edgeIds.add(new EdgeId(toUUID(strEdgeId)));
  463 + }
  464 + }
  465 +
  466 + Set<EdgeId> addedEdgeIds = new HashSet<>();
  467 + Set<EdgeId> removedEdgeIds = new HashSet<>();
  468 + for (EdgeId edgeId : edgeIds) {
  469 + if (!ruleChain.isAssignedToEdge(edgeId)) {
  470 + addedEdgeIds.add(edgeId);
  471 + }
  472 + }
  473 +
  474 + Set<ShortEdgeInfo> assignedEdges = ruleChain.getAssignedEdges();
  475 + if (assignedEdges != null) {
  476 + for (ShortEdgeInfo edgeInfo : assignedEdges) {
  477 + if (!edgeIds.contains(edgeInfo.getEdgeId())) {
  478 + removedEdgeIds.add(edgeInfo.getEdgeId());
  479 + }
  480 + }
  481 + }
  482 +
  483 + if (addedEdgeIds.isEmpty() && removedEdgeIds.isEmpty()) {
  484 + return ruleChain;
  485 + } else {
  486 + RuleChain savedRuleChain = null;
  487 + for (EdgeId edgeId : addedEdgeIds) {
  488 + savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId));
  489 + ShortEdgeInfo edgeInfo = savedRuleChain.getAssignedEdgeInfo(edgeId);
  490 + logEntityAction(ruleChainId, savedRuleChain,
  491 + null,
  492 + ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle());
  493 + }
  494 + for (EdgeId edgeId : removedEdgeIds) {
  495 + ShortEdgeInfo edgeInfo = ruleChain.getAssignedEdgeInfo(edgeId);
  496 + savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId));
  497 + logEntityAction(ruleChainId, ruleChain,
  498 + null,
  499 + ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle());
  500 +
  501 + }
  502 + return savedRuleChain;
  503 + }
  504 + } catch (Exception e) {
  505 +
  506 + logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
  507 + null,
  508 + ActionType.ASSIGNED_TO_EDGE, e, strRuleChainId);
  509 +
  510 + throw handleException(e);
  511 + }
  512 + }
  513 +
  514 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  515 + @RequestMapping(value = "/ruleChain/{ruleChainId}/edges/add", method = RequestMethod.POST)
  516 + @ResponseBody
  517 + public RuleChain addRuleChainEdges(@PathVariable(RULE_CHAIN_ID) String strRuleChainId,
  518 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  519 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  520 + try {
  521 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  522 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.ASSIGN_TO_EDGE);
  523 +
  524 + Set<EdgeId> edgeIds = new HashSet<>();
  525 + if (strEdgeIds != null) {
  526 + for (String strEdgeId : strEdgeIds) {
  527 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  528 + if (!ruleChain.isAssignedToEdge(edgeId)) {
  529 + edgeIds.add(edgeId);
  530 + }
  531 + }
  532 + }
  533 +
  534 + if (edgeIds.isEmpty()) {
  535 + return ruleChain;
  536 + } else {
  537 + RuleChain savedRuleChain = null;
  538 + for (EdgeId edgeId : edgeIds) {
  539 + savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId));
  540 + ShortEdgeInfo edgeInfo = savedRuleChain.getAssignedEdgeInfo(edgeId);
  541 + logEntityAction(ruleChainId, savedRuleChain,
  542 + null,
  543 + ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle());
  544 + }
  545 + return savedRuleChain;
  546 + }
  547 + } catch (Exception e) {
  548 +
  549 + logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
  550 + null,
  551 + ActionType.ASSIGNED_TO_EDGE, e, strRuleChainId);
  552 +
  553 + throw handleException(e);
  554 + }
  555 + }
  556 +
  557 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  558 + @RequestMapping(value = "/ruleChain/{ruleChainId}/edges/remove", method = RequestMethod.POST)
  559 + @ResponseBody
  560 + public RuleChain removeRuleChainEdges(@PathVariable(RULE_CHAIN_ID) String strRuleChainId,
  561 + @RequestBody String[] strEdgeIds) throws ThingsboardException {
  562 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  563 + try {
  564 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  565 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.UNASSIGN_FROM_EDGE);
  566 +
  567 + Set<EdgeId> edgeIds = new HashSet<>();
  568 + if (strEdgeIds != null) {
  569 + for (String strEdgeId : strEdgeIds) {
  570 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  571 + if (ruleChain.isAssignedToEdge(edgeId)) {
  572 + edgeIds.add(edgeId);
  573 + }
  574 + }
  575 + }
  576 +
  577 + if (edgeIds.isEmpty()) {
  578 + return ruleChain;
  579 + } else {
  580 + RuleChain savedRuleChain = null;
  581 + for (EdgeId edgeId : edgeIds) {
  582 + ShortEdgeInfo edgeInfo = ruleChain.getAssignedEdgeInfo(edgeId);
  583 + savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId));
  584 + logEntityAction(ruleChainId, ruleChain,
  585 + null,
  586 + ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, edgeId.toString(), edgeInfo.getTitle());
  587 +
  588 + }
  589 + return savedRuleChain;
  590 + }
  591 + } catch (Exception e) {
  592 +
  593 + logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
  594 + null,
  595 + ActionType.UNASSIGNED_FROM_EDGE, e, strRuleChainId);
  596 +
  597 + throw handleException(e);
  598 + }
  599 + }
  600 +
  601 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  602 + @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
  603 + @ResponseBody
  604 + public PageData<RuleChain> getEdgeRuleChains(
  605 + @PathVariable("edgeId") String strEdgeId,
  606 + @RequestParam int pageSize,
  607 + @RequestParam int page,
  608 + @RequestParam(required = false) String textSearch,
  609 + @RequestParam(required = false) String sortProperty,
  610 + @RequestParam(required = false) String sortOrder,
  611 + @RequestParam(required = false) Long startTime,
  612 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
  613 + checkParameter("edgeId", strEdgeId);
  614 + try {
  615 + TenantId tenantId = getCurrentUser().getTenantId();
  616 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  617 + checkEdgeId(edgeId, Operation.READ);
  618 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  619 + return checkNotNull(ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
  620 + } catch (Exception e) {
  621 + throw handleException(e);
  622 + }
  623 + }
  624 +
375 } 625 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.edge;
  17 +
  18 +import lombok.Data;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.context.annotation.Lazy;
  21 +import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.actors.service.ActorService;
  23 +import org.thingsboard.server.dao.alarm.AlarmService;
  24 +import org.thingsboard.server.dao.asset.AssetService;
  25 +import org.thingsboard.server.dao.attributes.AttributesService;
  26 +import org.thingsboard.server.dao.customer.CustomerService;
  27 +import org.thingsboard.server.dao.dashboard.DashboardService;
  28 +import org.thingsboard.server.dao.device.DeviceService;
  29 +import org.thingsboard.server.dao.edge.EdgeService;
  30 +import org.thingsboard.server.dao.entityview.EntityViewService;
  31 +import org.thingsboard.server.dao.relation.RelationService;
  32 +
  33 +@Component
  34 +@Data
  35 +public class EdgeContextComponent {
  36 +
  37 + @Lazy
  38 + @Autowired
  39 + private EdgeService edgeService;
  40 +
  41 + @Lazy
  42 + @Autowired
  43 + private AssetService assetService;
  44 +
  45 + @Lazy
  46 + @Autowired
  47 + private DeviceService deviceService;
  48 +
  49 + @Lazy
  50 + @Autowired
  51 + private EntityViewService entityViewService;
  52 +
  53 + @Lazy
  54 + @Autowired
  55 + private AttributesService attributesService;
  56 +
  57 + @Lazy
  58 + @Autowired
  59 + private CustomerService customerService;
  60 +
  61 + @Lazy
  62 + @Autowired
  63 + private RelationService relationService;
  64 +
  65 + @Lazy
  66 + @Autowired
  67 + private AlarmService alarmService;
  68 +
  69 + @Lazy
  70 + @Autowired
  71 + private DashboardService dashboardService;
  72 +
  73 + @Lazy
  74 + @Autowired
  75 + private ActorService actorService;
  76 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.edge.rpc;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import com.google.common.io.Resources;
  20 +import io.grpc.Server;
  21 +import io.grpc.ServerBuilder;
  22 +import io.grpc.stub.StreamObserver;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.springframework.beans.factory.annotation.Autowired;
  25 +import org.springframework.beans.factory.annotation.Value;
  26 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  27 +import org.springframework.stereotype.Service;
  28 +import org.thingsboard.server.common.data.id.EdgeId;
  29 +import org.thingsboard.server.common.data.id.RuleChainId;
  30 +import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.gen.edge.EdgeRpcServiceGrpc;
  32 +import org.thingsboard.server.gen.edge.RequestMsg;
  33 +import org.thingsboard.server.gen.edge.ResponseMsg;
  34 +import org.thingsboard.server.service.edge.EdgeContextComponent;
  35 +
  36 +import javax.annotation.PostConstruct;
  37 +import javax.annotation.PreDestroy;
  38 +import java.io.File;
  39 +import java.io.IOException;
  40 +import java.util.Map;
  41 +import java.util.concurrent.ConcurrentHashMap;
  42 +import java.util.concurrent.ExecutorService;
  43 +import java.util.concurrent.Executors;
  44 +
  45 +@Service
  46 +@Slf4j
  47 +@ConditionalOnProperty(prefix = "edges.rpc", value = "enabled", havingValue = "true")
  48 +public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase {
  49 +
  50 + private final Map<EdgeId, EdgeGrpcSession> sessions = new ConcurrentHashMap<>();
  51 + private static final ObjectMapper objectMapper = new ObjectMapper();
  52 +
  53 + @Value("${edges.rpc.port}")
  54 + private int rpcPort;
  55 + @Value("${edges.rpc.ssl.enabled}")
  56 + private boolean sslEnabled;
  57 + @Value("${edges.rpc.ssl.cert}")
  58 + private String certFileResource;
  59 + @Value("${edges.rpc.ssl.privateKey}")
  60 + private String privateKeyResource;
  61 +
  62 + @Autowired
  63 + private EdgeContextComponent ctx;
  64 +
  65 + private Server server;
  66 +
  67 + private ExecutorService executor;
  68 +
  69 + @PostConstruct
  70 + public void init() {
  71 + log.info("Initializing Edge RPC service!");
  72 + ServerBuilder builder = ServerBuilder.forPort(rpcPort).addService(this);
  73 + if (sslEnabled) {
  74 + try {
  75 + File certFile = new File(Resources.getResource(certFileResource).toURI());
  76 + File privateKeyFile = new File(Resources.getResource(privateKeyResource).toURI());
  77 + builder.useTransportSecurity(certFile, privateKeyFile);
  78 + } catch (Exception e) {
  79 + log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e);
  80 + throw new RuntimeException("Unable to set up SSL context!", e);
  81 + }
  82 + }
  83 + server = builder.build();
  84 + log.info("Going to start Edge RPC server using port: {}", rpcPort);
  85 + try {
  86 + server.start();
  87 + } catch (IOException e) {
  88 + log.error("Failed to start Edge RPC server!", e);
  89 + throw new RuntimeException("Failed to start Edge RPC server!");
  90 + }
  91 + log.info("Edge RPC service initialized!");
  92 + executor = Executors.newSingleThreadExecutor();
  93 + processHandleMessages();
  94 + }
  95 +
  96 + @PreDestroy
  97 + public void destroy() {
  98 + if (server != null) {
  99 + server.shutdownNow();
  100 + }
  101 + }
  102 +
  103 + @Override
  104 + public StreamObserver<RequestMsg> handleMsgs(StreamObserver<ResponseMsg> outputStream) {
  105 + return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, objectMapper).getInputStream();
  106 + }
  107 +
  108 + private void onEdgeConnect(EdgeId edgeId, EdgeGrpcSession edgeGrpcSession) {
  109 + sessions.put(edgeId, edgeGrpcSession);
  110 + }
  111 +
  112 + private void processHandleMessages() {
  113 + executor.submit(() -> {
  114 + while (!Thread.interrupted()) {
  115 + try {
  116 + for (EdgeGrpcSession session : sessions.values()) {
  117 + session.processHandleMessages();
  118 + }
  119 + } catch (Exception e) {
  120 + log.warn("Failed to process messages handling!", e);
  121 + }
  122 + }
  123 + });
  124 + }
  125 +
  126 + private void onEdgeDisconnect(EdgeId edgeId) {
  127 + sessions.remove(edgeId);
  128 + }
  129 +
  130 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.edge.rpc;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.JsonProcessingException;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import com.fasterxml.jackson.databind.node.ObjectNode;
  22 +import com.google.common.util.concurrent.Futures;
  23 +import com.google.common.util.concurrent.ListenableFuture;
  24 +import com.google.protobuf.ByteString;
  25 +import io.grpc.stub.StreamObserver;
  26 +import lombok.Data;
  27 +import lombok.extern.slf4j.Slf4j;
  28 +import org.thingsboard.server.common.data.Customer;
  29 +import org.thingsboard.server.common.data.Dashboard;
  30 +import org.thingsboard.server.common.data.DataConstants;
  31 +import org.thingsboard.server.common.data.Device;
  32 +import org.thingsboard.server.common.data.EntityType;
  33 +import org.thingsboard.server.common.data.EntityView;
  34 +import org.thingsboard.server.common.data.Event;
  35 +import org.thingsboard.server.common.data.User;
  36 +import org.thingsboard.server.common.data.alarm.Alarm;
  37 +import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  38 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
  39 +import org.thingsboard.server.common.data.asset.Asset;
  40 +import org.thingsboard.server.common.data.edge.Edge;
  41 +import org.thingsboard.server.common.data.edge.EdgeQueueEntry;
  42 +import org.thingsboard.server.common.data.id.AssetId;
  43 +import org.thingsboard.server.common.data.id.CustomerId;
  44 +import org.thingsboard.server.common.data.id.DeviceId;
  45 +import org.thingsboard.server.common.data.id.EdgeId;
  46 +import org.thingsboard.server.common.data.id.EntityId;
  47 +import org.thingsboard.server.common.data.id.EntityViewId;
  48 +import org.thingsboard.server.common.data.id.TenantId;
  49 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  50 +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
  51 +import org.thingsboard.server.common.data.kv.DataType;
  52 +import org.thingsboard.server.common.data.kv.LongDataEntry;
  53 +import org.thingsboard.server.common.data.page.PageData;
  54 +import org.thingsboard.server.common.data.page.TimePageLink;
  55 +import org.thingsboard.server.common.data.relation.EntityRelation;
  56 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  57 +import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
  58 +import org.thingsboard.server.common.data.rule.RuleChain;
  59 +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
  60 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  61 +import org.thingsboard.server.common.data.rule.RuleNode;
  62 +import org.thingsboard.server.common.msg.TbMsg;
  63 +import org.thingsboard.server.common.msg.TbMsgDataType;
  64 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  65 +import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
  66 +import org.thingsboard.server.common.msg.session.SessionMsgType;
  67 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  68 +import org.thingsboard.server.dao.util.mapping.JacksonUtil;
  69 +import org.thingsboard.server.gen.edge.AlarmUpdateMsg;
  70 +import org.thingsboard.server.gen.edge.AssetUpdateMsg;
  71 +import org.thingsboard.server.gen.edge.ConnectRequestMsg;
  72 +import org.thingsboard.server.gen.edge.ConnectResponseCode;
  73 +import org.thingsboard.server.gen.edge.ConnectResponseMsg;
  74 +import org.thingsboard.server.gen.edge.CustomerUpdateMsg;
  75 +import org.thingsboard.server.gen.edge.DashboardUpdateMsg;
  76 +import org.thingsboard.server.gen.edge.DeviceUpdateMsg;
  77 +import org.thingsboard.server.gen.edge.DownlinkMsg;
  78 +import org.thingsboard.server.gen.edge.EdgeConfiguration;
  79 +import org.thingsboard.server.gen.edge.EntityDataProto;
  80 +import org.thingsboard.server.gen.edge.EntityUpdateMsg;
  81 +import org.thingsboard.server.gen.edge.EntityViewUpdateMsg;
  82 +import org.thingsboard.server.gen.edge.NodeConnectionInfoProto;
  83 +import org.thingsboard.server.gen.edge.RequestMsg;
  84 +import org.thingsboard.server.gen.edge.RequestMsgType;
  85 +import org.thingsboard.server.gen.edge.ResponseMsg;
  86 +import org.thingsboard.server.gen.edge.RuleChainConnectionInfoProto;
  87 +import org.thingsboard.server.gen.edge.RuleChainMetadataUpdateMsg;
  88 +import org.thingsboard.server.gen.edge.RuleChainUpdateMsg;
  89 +import org.thingsboard.server.gen.edge.RuleNodeProto;
  90 +import org.thingsboard.server.gen.edge.UpdateMsgType;
  91 +import org.thingsboard.server.gen.edge.UplinkMsg;
  92 +import org.thingsboard.server.gen.edge.UplinkResponseMsg;
  93 +import org.thingsboard.server.gen.edge.UserUpdateMsg;
  94 +import org.thingsboard.server.service.edge.EdgeContextComponent;
  95 +
  96 +import java.io.IOException;
  97 +import java.util.ArrayList;
  98 +import java.util.Collections;
  99 +import java.util.List;
  100 +import java.util.Optional;
  101 +import java.util.UUID;
  102 +import java.util.concurrent.ExecutionException;
  103 +import java.util.concurrent.locks.ReentrantLock;
  104 +import java.util.function.BiConsumer;
  105 +import java.util.function.Consumer;
  106 +
  107 +import static org.thingsboard.server.gen.edge.UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE;
  108 +
  109 +@Slf4j
  110 +@Data
  111 +public final class EdgeGrpcSession implements Cloneable {
  112 +
  113 + private static final ReentrantLock entityCreationLock = new ReentrantLock();
  114 +
  115 + private final UUID sessionId;
  116 + private final BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener;
  117 + private final Consumer<EdgeId> sessionCloseListener;
  118 + private final ObjectMapper objectMapper;
  119 +
  120 + private EdgeContextComponent ctx;
  121 + private Edge edge;
  122 + private StreamObserver<RequestMsg> inputStream;
  123 + private StreamObserver<ResponseMsg> outputStream;
  124 + private boolean connected;
  125 +
  126 + EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver<ResponseMsg> outputStream, BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener,
  127 + Consumer<EdgeId> sessionCloseListener, ObjectMapper objectMapper) {
  128 + this.sessionId = UUID.randomUUID();
  129 + this.ctx = ctx;
  130 + this.outputStream = outputStream;
  131 + this.sessionOpenListener = sessionOpenListener;
  132 + this.sessionCloseListener = sessionCloseListener;
  133 + this.objectMapper = objectMapper;
  134 + initInputStream();
  135 + }
  136 +
  137 + private void initInputStream() {
  138 + this.inputStream = new StreamObserver<RequestMsg>() {
  139 + @Override
  140 + public void onNext(RequestMsg requestMsg) {
  141 + if (!connected && requestMsg.getMsgType().equals(RequestMsgType.CONNECT_RPC_MESSAGE)) {
  142 + ConnectResponseMsg responseMsg = processConnect(requestMsg.getConnectRequestMsg());
  143 + outputStream.onNext(ResponseMsg.newBuilder()
  144 + .setConnectResponseMsg(responseMsg)
  145 + .build());
  146 + if (ConnectResponseCode.ACCEPTED != responseMsg.getResponseCode()) {
  147 + outputStream.onError(new RuntimeException(responseMsg.getErrorMsg()));
  148 + }
  149 + }
  150 + if (connected) {
  151 + if (requestMsg.getMsgType().equals(RequestMsgType.UPLINK_RPC_MESSAGE) && requestMsg.hasUplinkMsg()) {
  152 + outputStream.onNext(ResponseMsg.newBuilder()
  153 + .setUplinkResponseMsg(processUplinkMsg(requestMsg.getUplinkMsg()))
  154 + .build());
  155 + }
  156 + }
  157 + }
  158 +
  159 + @Override
  160 + public void onError(Throwable t) {
  161 + log.error("Failed to deliver message from client!", t);
  162 + }
  163 +
  164 + @Override
  165 + public void onCompleted() {
  166 + sessionCloseListener.accept(edge.getId());
  167 + outputStream.onCompleted();
  168 + }
  169 + };
  170 + }
  171 +
  172 +
  173 + void processHandleMessages() throws ExecutionException, InterruptedException {
  174 + Long queueStartTs = getQueueStartTs().get();
  175 + // TODO: this 100 value must be changed properly
  176 + TimePageLink pageLink = new TimePageLink(30, 0, "", null, queueStartTs + 1000, null);
  177 + PageData<Event> pageData;
  178 + UUID ifOffset = null;
  179 + do {
  180 + pageData = ctx.getEdgeService().findQueueEvents(edge.getTenantId(), edge.getId(), pageLink);
  181 + if (!pageData.getData().isEmpty()) {
  182 + log.trace("[{}] [{}] event(s) are going to be processed.", this.sessionId, pageData.getData().size());
  183 + for (Event event : pageData.getData()) {
  184 + log.trace("[{}] Processing event [{}]", this.sessionId, event);
  185 + EdgeQueueEntry entry;
  186 + try {
  187 + entry = objectMapper.treeToValue(event.getBody(), EdgeQueueEntry.class);
  188 +
  189 + UpdateMsgType msgType = getResponseMsgType(entry.getType());
  190 + switch (msgType) {
  191 + case ENTITY_DELETED_RPC_MESSAGE:
  192 + case ENTITY_UPDATED_RPC_MESSAGE:
  193 + case ENTITY_CREATED_RPC_MESSAGE:
  194 + case ALARM_ACK_RPC_MESSAGE:
  195 + case ALARM_CLEAR_RPC_MESSAGE:
  196 + processEntityCRUDMessage(entry, msgType);
  197 + break;
  198 + case RULE_CHAIN_CUSTOM_MESSAGE:
  199 + processCustomDownlinkMessage(entry);
  200 + break;
  201 + }
  202 + if (ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) {
  203 + pushEntityAttributesToEdge(entry);
  204 + }
  205 + } catch (Exception e) {
  206 + log.error("Exception during processing records from queue", e);
  207 + }
  208 + ifOffset = event.getUuidId();
  209 + }
  210 + }
  211 + if (pageData.hasNext()) {
  212 + pageLink = pageLink.nextPageLink();
  213 + }
  214 + } while (pageData.hasNext());
  215 +
  216 + if (ifOffset != null) {
  217 + Long newStartTs = UUIDs.unixTimestamp(ifOffset);
  218 + updateQueueStartTs(newStartTs);
  219 + }
  220 + try {
  221 + Thread.sleep(1000);
  222 + } catch (InterruptedException e) {
  223 + log.error("Error during sleep", e);
  224 + }
  225 + }
  226 +
  227 + private void pushEntityAttributesToEdge(EdgeQueueEntry entry) throws IOException {
  228 + EntityId entityId = null;
  229 + String entityName = null;
  230 + switch (entry.getEntityType()) {
  231 + case EDGE:
  232 + entityId = objectMapper.readValue(entry.getData(), Edge.class).getId();
  233 + break;
  234 + case DEVICE:
  235 + entityId = objectMapper.readValue(entry.getData(), Device.class).getId();
  236 + break;
  237 + case ASSET:
  238 + entityId = objectMapper.readValue(entry.getData(), Asset.class).getId();
  239 + break;
  240 + case ENTITY_VIEW:
  241 + entityId = objectMapper.readValue(entry.getData(), EntityView.class).getId();
  242 + break;
  243 + case DASHBOARD:
  244 + entityId = objectMapper.readValue(entry.getData(), Dashboard.class).getId();
  245 + break;
  246 + }
  247 + if (entityId != null) {
  248 + ListenableFuture<List<AttributeKvEntry>> ssAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SERVER_SCOPE);
  249 + EntityId finalEntityId = entityId;
  250 + Futures.transform(ssAttrFuture, ssAttributes -> {
  251 + if (ssAttributes != null && !ssAttributes.isEmpty()) {
  252 + try {
  253 + TbMsgMetaData metaData = new TbMsgMetaData();
  254 + ObjectNode entityNode = objectMapper.createObjectNode();
  255 + metaData.putValue("scope", DataConstants.SERVER_SCOPE);
  256 + for (AttributeKvEntry attr : ssAttributes) {
  257 + if (attr.getDataType() == DataType.BOOLEAN) {
  258 + entityNode.put(attr.getKey(), attr.getBooleanValue().get());
  259 + } else if (attr.getDataType() == DataType.DOUBLE) {
  260 + entityNode.put(attr.getKey(), attr.getDoubleValue().get());
  261 + } else if (attr.getDataType() == DataType.LONG) {
  262 + entityNode.put(attr.getKey(), attr.getLongValue().get());
  263 + } else {
  264 + entityNode.put(attr.getKey(), attr.getValueAsString());
  265 + }
  266 + }
  267 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), DataConstants.ATTRIBUTES_UPDATED, finalEntityId, metaData, TbMsgDataType.JSON
  268 + , objectMapper.writeValueAsString(entityNode)
  269 + , null, null, 0L);
  270 + log.debug("Sending donwlink entity data msg, entityName [{}], tbMsg [{}]", entityName, tbMsg);
  271 + outputStream.onNext(ResponseMsg.newBuilder()
  272 + .setDownlinkMsg(constructDownlinkEntityDataMsg(entityName, tbMsg))
  273 + .build());
  274 + } catch (Exception e) {
  275 + log.error("[{}] Failed to send attribute updates to the edge", edge.getName(), e);
  276 + }
  277 + }
  278 + return null;
  279 + });
  280 + ListenableFuture<List<AttributeKvEntry>> shAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.SHARED_SCOPE);
  281 + ListenableFuture<List<AttributeKvEntry>> clAttrFuture = ctx.getAttributesService().findAll(edge.getTenantId(), entityId, DataConstants.CLIENT_SCOPE);
  282 + }
  283 + }
  284 +
  285 + private void processCustomDownlinkMessage(EdgeQueueEntry entry) throws IOException {
  286 + log.trace("Executing processCustomDownlinkMessage, entry [{}]", entry);
  287 + TbMsg tbMsg = objectMapper.readValue(entry.getData(), TbMsg.class);
  288 + String entityName = null;
  289 + switch (entry.getEntityType()) {
  290 + case DEVICE:
  291 + Device device = ctx.getDeviceService().findDeviceById(edge.getTenantId(), new DeviceId(tbMsg.getOriginator().getId()));
  292 + entityName = device.getName();
  293 + break;
  294 + case ASSET:
  295 + Asset asset = ctx.getAssetService().findAssetById(edge.getTenantId(), new AssetId(tbMsg.getOriginator().getId()));
  296 + entityName = asset.getName();
  297 + break;
  298 + case ENTITY_VIEW:
  299 + EntityView entityView = ctx.getEntityViewService().findEntityViewById(edge.getTenantId(), new EntityViewId(tbMsg.getOriginator().getId()));
  300 + entityName = entityView.getName();
  301 + break;
  302 +
  303 + }
  304 + if (entityName != null) {
  305 + log.debug("Sending donwlink entity data msg, entityName [{}], tbMsg [{}]", entityName, tbMsg);
  306 + outputStream.onNext(ResponseMsg.newBuilder()
  307 + .setDownlinkMsg(constructDownlinkEntityDataMsg(entityName, tbMsg))
  308 + .build());
  309 + }
  310 + }
  311 +
  312 + private void processEntityCRUDMessage(EdgeQueueEntry entry, UpdateMsgType msgType) throws java.io.IOException {
  313 + log.trace("Executing processEntityCRUDMessage, entry [{}], msgType [{}]", entry, msgType);
  314 + switch (entry.getEntityType()) {
  315 + case EDGE:
  316 + Edge edge = objectMapper.readValue(entry.getData(), Edge.class);
  317 + onEdgeUpdated(msgType, edge);
  318 + break;
  319 + case DEVICE:
  320 + Device device = objectMapper.readValue(entry.getData(), Device.class);
  321 + onDeviceUpdated(msgType, device);
  322 + break;
  323 + case ASSET:
  324 + Asset asset = objectMapper.readValue(entry.getData(), Asset.class);
  325 + onAssetUpdated(msgType, asset);
  326 + break;
  327 + case ENTITY_VIEW:
  328 + EntityView entityView = objectMapper.readValue(entry.getData(), EntityView.class);
  329 + onEntityViewUpdated(msgType, entityView);
  330 + break;
  331 + case DASHBOARD:
  332 + Dashboard dashboard = objectMapper.readValue(entry.getData(), Dashboard.class);
  333 + onDashboardUpdated(msgType, dashboard);
  334 + break;
  335 + case RULE_CHAIN:
  336 + RuleChain ruleChain = objectMapper.readValue(entry.getData(), RuleChain.class);
  337 + onRuleChainUpdated(msgType, ruleChain);
  338 + break;
  339 + case RULE_CHAIN_METADATA:
  340 + RuleChainMetaData ruleChainMetaData = objectMapper.readValue(entry.getData(), RuleChainMetaData.class);
  341 + onRuleChainMetadataUpdated(msgType, ruleChainMetaData);
  342 + break;
  343 + case ALARM:
  344 + Alarm alarm = objectMapper.readValue(entry.getData(), Alarm.class);
  345 + onAlarmUpdated(msgType, alarm);
  346 + break;
  347 + }
  348 + }
  349 +
  350 + private void updateQueueStartTs(Long newStartTs) {
  351 + List<AttributeKvEntry> attributes = Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry("queueStartTs", newStartTs), System.currentTimeMillis()));
  352 + ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes);
  353 + }
  354 +
  355 + private ListenableFuture<Long> getQueueStartTs() {
  356 + ListenableFuture<Optional<AttributeKvEntry>> future =
  357 + ctx.getAttributesService().find(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, "queueStartTs");
  358 + return Futures.transform(future, attributeKvEntryOpt -> {
  359 + if (attributeKvEntryOpt != null && attributeKvEntryOpt.isPresent()) {
  360 + AttributeKvEntry attributeKvEntry = attributeKvEntryOpt.get();
  361 + return attributeKvEntry.getLongValue().isPresent() ? attributeKvEntry.getLongValue().get() : 0L;
  362 + } else {
  363 + return 0L;
  364 + }
  365 + });
  366 + }
  367 +
  368 + private void onEdgeUpdated(UpdateMsgType msgType, Edge edge) {
  369 + // TODO: voba add configuration update to edge
  370 + this.edge = edge;
  371 + }
  372 +
  373 + private void onDeviceUpdated(UpdateMsgType msgType, Device device) {
  374 + EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
  375 + .setDeviceUpdateMsg(constructDeviceUpdatedMsg(msgType, device))
  376 + .build();
  377 + outputStream.onNext(ResponseMsg.newBuilder()
  378 + .setEntityUpdateMsg(entityUpdateMsg)
  379 + .build());
  380 + }
  381 +
  382 + private void onAssetUpdated(UpdateMsgType msgType, Asset asset) {
  383 + EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
  384 + .setAssetUpdateMsg(constructAssetUpdatedMsg(msgType, asset))
  385 + .build();
  386 + outputStream.onNext(ResponseMsg.newBuilder()
  387 + .setEntityUpdateMsg(entityUpdateMsg)
  388 + .build());
  389 + }
  390 +
  391 + private void onEntityViewUpdated(UpdateMsgType msgType, EntityView entityView) {
  392 + EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
  393 + .setEntityViewUpdateMsg(constructEntityViewUpdatedMsg(msgType, entityView))
  394 + .build();
  395 + outputStream.onNext(ResponseMsg.newBuilder()
  396 + .setEntityUpdateMsg(entityUpdateMsg)
  397 + .build());
  398 + }
  399 +
  400 + private void onRuleChainUpdated(UpdateMsgType msgType, RuleChain ruleChain) {
  401 + EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
  402 + .setRuleChainUpdateMsg(constructRuleChainUpdatedMsg(msgType, ruleChain))
  403 + .build();
  404 + outputStream.onNext(ResponseMsg.newBuilder()
  405 + .setEntityUpdateMsg(entityUpdateMsg)
  406 + .build());
  407 + }
  408 +
  409 + private void onRuleChainMetadataUpdated(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData) {
  410 + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = constructRuleChainMetadataUpdatedMsg(msgType, ruleChainMetaData);
  411 + if (ruleChainMetadataUpdateMsg != null) {
  412 + EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
  413 + .setRuleChainMetadataUpdateMsg(ruleChainMetadataUpdateMsg)
  414 + .build();
  415 + outputStream.onNext(ResponseMsg.newBuilder()
  416 + .setEntityUpdateMsg(entityUpdateMsg)
  417 + .build());
  418 + }
  419 + }
  420 +
  421 + private void onDashboardUpdated(UpdateMsgType msgType, Dashboard dashboard) {
  422 + EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
  423 + .setDashboardUpdateMsg(constructDashboardUpdatedMsg(msgType, dashboard))
  424 + .build();
  425 + outputStream.onNext(ResponseMsg.newBuilder()
  426 + .setEntityUpdateMsg(entityUpdateMsg)
  427 + .build());
  428 + }
  429 +
  430 + private void onAlarmUpdated(UpdateMsgType msgType, Alarm alarm) {
  431 + EntityUpdateMsg entityUpdateMsg = EntityUpdateMsg.newBuilder()
  432 + .setAlarmUpdateMsg(constructAlarmUpdatedMsg(msgType, alarm))
  433 + .build();
  434 + outputStream.onNext(ResponseMsg.newBuilder()
  435 + .setEntityUpdateMsg(entityUpdateMsg)
  436 + .build());
  437 + }
  438 +
  439 + private AlarmUpdateMsg constructAlarmUpdatedMsg(UpdateMsgType msgType, Alarm alarm) {
  440 + String entityName = null;
  441 + switch (alarm.getOriginator().getEntityType()) {
  442 + case DEVICE:
  443 + entityName = ctx.getDeviceService().findDeviceById(edge.getTenantId(), new DeviceId(alarm.getOriginator().getId())).getName();
  444 + break;
  445 + case ASSET:
  446 + entityName = ctx.getAssetService().findAssetById(edge.getTenantId(), new AssetId(alarm.getOriginator().getId())).getName();
  447 + break;
  448 + case ENTITY_VIEW:
  449 + entityName = ctx.getEntityViewService().findEntityViewById(edge.getTenantId(), new EntityViewId(alarm.getOriginator().getId())).getName();
  450 + break;
  451 + }
  452 + AlarmUpdateMsg.Builder builder = AlarmUpdateMsg.newBuilder()
  453 + .setMsgType(msgType)
  454 + .setName(alarm.getName())
  455 + .setType(alarm.getType())
  456 + .setOriginatorName(entityName)
  457 + .setOriginatorType(alarm.getOriginator().getEntityType().name())
  458 + .setSeverity(alarm.getSeverity().name())
  459 + .setStatus(alarm.getStatus().name())
  460 + .setStartTs(alarm.getStartTs())
  461 + .setEndTs(alarm.getEndTs())
  462 + .setAckTs(alarm.getAckTs())
  463 + .setClearTs(alarm.getClearTs())
  464 + .setDetails(JacksonUtil.toString(alarm.getDetails()))
  465 + .setPropagate(alarm.isPropagate());
  466 + return builder.build();
  467 + }
  468 +
  469 + private UpdateMsgType getResponseMsgType(String msgType) {
  470 + if (msgType.equals(SessionMsgType.POST_TELEMETRY_REQUEST.name()) ||
  471 + msgType.equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name()) ||
  472 + msgType.equals(DataConstants.ATTRIBUTES_UPDATED) ||
  473 + msgType.equals(DataConstants.ATTRIBUTES_DELETED)) {
  474 + return UpdateMsgType.RULE_CHAIN_CUSTOM_MESSAGE;
  475 + } else {
  476 + switch (msgType) {
  477 + case DataConstants.ENTITY_UPDATED:
  478 + return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE;
  479 + case DataConstants.ENTITY_CREATED:
  480 + case DataConstants.ENTITY_ASSIGNED_TO_EDGE:
  481 + return ENTITY_CREATED_RPC_MESSAGE;
  482 + case DataConstants.ENTITY_DELETED:
  483 + case DataConstants.ENTITY_UNASSIGNED_FROM_EDGE:
  484 + return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
  485 + case DataConstants.ALARM_ACK:
  486 + return UpdateMsgType.ALARM_ACK_RPC_MESSAGE;
  487 + case DataConstants.ALARM_CLEAR:
  488 + return UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE;
  489 + default:
  490 + throw new RuntimeException("Unsupported msgType [" + msgType + "]");
  491 + }
  492 + }
  493 + }
  494 +
  495 + private RuleChainUpdateMsg constructRuleChainUpdatedMsg(UpdateMsgType msgType, RuleChain ruleChain) {
  496 + RuleChainUpdateMsg.Builder builder = RuleChainUpdateMsg.newBuilder()
  497 + .setMsgType(msgType)
  498 + .setIdMSB(ruleChain.getId().getId().getMostSignificantBits())
  499 + .setIdLSB(ruleChain.getId().getId().getLeastSignificantBits())
  500 + .setName(ruleChain.getName())
  501 + .setRoot(ruleChain.getId().equals(edge.getRootRuleChainId()))
  502 + .setDebugMode(ruleChain.isDebugMode())
  503 + .setConfiguration(JacksonUtil.toString(ruleChain.getConfiguration()));
  504 + if (ruleChain.getFirstRuleNodeId() != null) {
  505 + builder.setFirstRuleNodeIdMSB(ruleChain.getFirstRuleNodeId().getId().getMostSignificantBits())
  506 + .setFirstRuleNodeIdLSB(ruleChain.getFirstRuleNodeId().getId().getLeastSignificantBits());
  507 + }
  508 + return builder.build();
  509 + }
  510 +
  511 + private DownlinkMsg constructDownlinkEntityDataMsg(String entityName, TbMsg tbMsg) {
  512 + EntityDataProto entityData = EntityDataProto.newBuilder()
  513 + .setEntityName(entityName)
  514 + .setTbMsg(ByteString.copyFrom(TbMsg.toBytes(tbMsg))).build();
  515 +
  516 + DownlinkMsg.Builder builder = DownlinkMsg.newBuilder()
  517 + .addAllEntityData(Collections.singletonList(entityData));
  518 +
  519 + return builder.build();
  520 + }
  521 +
  522 + private RuleChainMetadataUpdateMsg constructRuleChainMetadataUpdatedMsg(UpdateMsgType msgType, RuleChainMetaData ruleChainMetaData) {
  523 + try {
  524 + RuleChainMetadataUpdateMsg.Builder builder = RuleChainMetadataUpdateMsg.newBuilder()
  525 + .setRuleChainIdMSB(ruleChainMetaData.getRuleChainId().getId().getMostSignificantBits())
  526 + .setRuleChainIdLSB(ruleChainMetaData.getRuleChainId().getId().getLeastSignificantBits())
  527 + .addAllNodes(constructNodes(ruleChainMetaData.getNodes()))
  528 + .addAllConnections(constructConnections(ruleChainMetaData.getConnections()))
  529 + .addAllRuleChainConnections(constructRuleChainConnections(ruleChainMetaData.getRuleChainConnections()));
  530 + if (ruleChainMetaData.getFirstNodeIndex() != null) {
  531 + builder.setFirstNodeIndex(ruleChainMetaData.getFirstNodeIndex());
  532 + }
  533 + builder.setMsgType(msgType);
  534 + return builder.build();
  535 + } catch (JsonProcessingException ex) {
  536 + log.error("Can't construct RuleChainMetadataUpdateMsg", ex);
  537 + }
  538 + return null;
  539 + }
  540 +
  541 + private List<RuleChainConnectionInfoProto> constructRuleChainConnections(List<RuleChainConnectionInfo> ruleChainConnections) throws JsonProcessingException {
  542 + List<RuleChainConnectionInfoProto> result = new ArrayList<>();
  543 + if (ruleChainConnections != null && !ruleChainConnections.isEmpty()) {
  544 + for (RuleChainConnectionInfo ruleChainConnectionInfo : ruleChainConnections) {
  545 + result.add(constructRuleChainConnection(ruleChainConnectionInfo));
  546 + }
  547 + }
  548 + return result;
  549 + }
  550 +
  551 + private RuleChainConnectionInfoProto constructRuleChainConnection(RuleChainConnectionInfo ruleChainConnectionInfo) throws JsonProcessingException {
  552 + return RuleChainConnectionInfoProto.newBuilder()
  553 + .setFromIndex(ruleChainConnectionInfo.getFromIndex())
  554 + .setTargetRuleChainIdMSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getMostSignificantBits())
  555 + .setTargetRuleChainIdLSB(ruleChainConnectionInfo.getTargetRuleChainId().getId().getLeastSignificantBits())
  556 + .setType(ruleChainConnectionInfo.getType())
  557 + .setAdditionalInfo(objectMapper.writeValueAsString(ruleChainConnectionInfo.getAdditionalInfo()))
  558 + .build();
  559 + }
  560 +
  561 + private List<NodeConnectionInfoProto> constructConnections(List<NodeConnectionInfo> connections) {
  562 + List<NodeConnectionInfoProto> result = new ArrayList<>();
  563 + if (connections != null && !connections.isEmpty()) {
  564 + for (NodeConnectionInfo connection : connections) {
  565 + result.add(constructConnection(connection));
  566 + }
  567 + }
  568 + return result;
  569 + }
  570 +
  571 + private NodeConnectionInfoProto constructConnection(NodeConnectionInfo connection) {
  572 + return NodeConnectionInfoProto.newBuilder()
  573 + .setFromIndex(connection.getFromIndex())
  574 + .setToIndex(connection.getToIndex())
  575 + .setType(connection.getType())
  576 + .build();
  577 + }
  578 +
  579 + private List<RuleNodeProto> constructNodes(List<RuleNode> nodes) throws JsonProcessingException {
  580 + List<RuleNodeProto> result = new ArrayList<>();
  581 + if (nodes != null && !nodes.isEmpty()) {
  582 + for (RuleNode node : nodes) {
  583 + result.add(constructNode(node));
  584 + }
  585 + }
  586 + return result;
  587 + }
  588 +
  589 + private RuleNodeProto constructNode(RuleNode node) throws JsonProcessingException {
  590 + return RuleNodeProto.newBuilder()
  591 + .setIdMSB(node.getId().getId().getMostSignificantBits())
  592 + .setIdLSB(node.getId().getId().getLeastSignificantBits())
  593 + .setType(node.getType())
  594 + .setName(node.getName())
  595 + .setDebugMode(node.isDebugMode())
  596 + .setConfiguration(objectMapper.writeValueAsString(node.getConfiguration()))
  597 + .setAdditionalInfo(objectMapper.writeValueAsString(node.getAdditionalInfo()))
  598 + .build();
  599 + }
  600 +
  601 + private DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) {
  602 + dashboard = ctx.getDashboardService().findDashboardById(edge.getTenantId(), dashboard.getId());
  603 + DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder()
  604 + .setMsgType(msgType)
  605 + .setIdMSB(dashboard.getId().getId().getMostSignificantBits())
  606 + .setIdLSB(dashboard.getId().getId().getLeastSignificantBits())
  607 + .setTitle(dashboard.getTitle())
  608 + .setConfiguration(JacksonUtil.toString(dashboard.getConfiguration()));
  609 + return builder.build();
  610 + }
  611 +
  612 + private CustomerUpdateMsg constructCustomerUpdatedMsg(UpdateMsgType msgType, Customer customer) {
  613 + CustomerUpdateMsg.Builder builder = CustomerUpdateMsg.newBuilder()
  614 + .setMsgType(msgType);
  615 + return builder.build();
  616 + }
  617 +
  618 + private UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user) {
  619 + UserUpdateMsg.Builder builder = UserUpdateMsg.newBuilder()
  620 + .setMsgType(msgType);
  621 + return builder.build();
  622 + }
  623 +
  624 + private DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device) {
  625 + DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder()
  626 + .setMsgType(msgType)
  627 + .setName(device.getName())
  628 + .setType(device.getType());
  629 + return builder.build();
  630 + }
  631 +
  632 + private AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset) {
  633 + AssetUpdateMsg.Builder builder = AssetUpdateMsg.newBuilder()
  634 + .setMsgType(msgType)
  635 + .setName(asset.getName())
  636 + .setType(asset.getType());
  637 + return builder.build();
  638 + }
  639 +
  640 + private EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView) {
  641 + String relatedName;
  642 + String relatedType;
  643 + org.thingsboard.server.gen.edge.EntityType relatedEntityType;
  644 + if (entityView.getEntityId().getEntityType().equals(EntityType.DEVICE)) {
  645 + Device device = ctx.getDeviceService().findDeviceById(entityView.getTenantId(), new DeviceId(entityView.getEntityId().getId()));
  646 + relatedName = device.getName();
  647 + relatedType = device.getType();
  648 + relatedEntityType = org.thingsboard.server.gen.edge.EntityType.DEVICE;
  649 + } else {
  650 + Asset asset = ctx.getAssetService().findAssetById(entityView.getTenantId(), new AssetId(entityView.getEntityId().getId()));
  651 + relatedName = asset.getName();
  652 + relatedType = asset.getType();
  653 + relatedEntityType = org.thingsboard.server.gen.edge.EntityType.ASSET;
  654 + }
  655 + EntityViewUpdateMsg.Builder builder = EntityViewUpdateMsg.newBuilder()
  656 + .setMsgType(msgType)
  657 + .setName(entityView.getName())
  658 + .setType(entityView.getType())
  659 + .setRelatedName(relatedName)
  660 + .setRelatedType(relatedType)
  661 + .setRelatedEntityType(relatedEntityType);
  662 + return builder.build();
  663 + }
  664 +
  665 + private UplinkResponseMsg processUplinkMsg(UplinkMsg uplinkMsg) {
  666 + try {
  667 + if (uplinkMsg.getEntityDataList() != null && !uplinkMsg.getEntityDataList().isEmpty()) {
  668 + for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) {
  669 + TbMsg tbMsg = null;
  670 + TbMsg originalTbMsg = TbMsg.fromBytes(entityData.getTbMsg().toByteArray());
  671 + switch (originalTbMsg.getOriginator().getEntityType()) {
  672 + case DEVICE:
  673 + String deviceName = entityData.getEntityName();
  674 + String deviceType = entityData.getEntityType();
  675 + Device device = getOrCreateDevice(deviceName, deviceType);
  676 + if (device != null) {
  677 + tbMsg = new TbMsg(UUIDs.timeBased(), originalTbMsg.getType(), device.getId(), originalTbMsg.getMetaData().copy(),
  678 + originalTbMsg.getDataType(), originalTbMsg.getData(), null, null, 0L);
  679 + }
  680 + break;
  681 + case ASSET:
  682 + String assetName = entityData.getEntityName();
  683 + Asset asset = ctx.getAssetService().findAssetByTenantIdAndName(edge.getTenantId(), assetName);
  684 + if (asset != null) {
  685 + tbMsg = new TbMsg(UUIDs.timeBased(), originalTbMsg.getType(), asset.getId(), originalTbMsg.getMetaData().copy(),
  686 + originalTbMsg.getDataType(), originalTbMsg.getData(), null, null, 0L);
  687 + }
  688 + break;
  689 + case ENTITY_VIEW:
  690 + String entityViewName = entityData.getEntityName();
  691 + EntityView entityView = ctx.getEntityViewService().findEntityViewByTenantIdAndName(edge.getTenantId(), entityViewName);
  692 + if (entityView != null) {
  693 + tbMsg = new TbMsg(UUIDs.timeBased(), originalTbMsg.getType(), entityView.getId(), originalTbMsg.getMetaData().copy(),
  694 + originalTbMsg.getDataType(), originalTbMsg.getData(), null, null, 0L);
  695 + }
  696 + break;
  697 + }
  698 + if (tbMsg != null) {
  699 + ctx.getActorService().onMsg(new SendToClusterMsg(tbMsg.getOriginator(), new ServiceToRuleEngineMsg(edge.getTenantId(), tbMsg)));
  700 + }
  701 + }
  702 + }
  703 + if (uplinkMsg.getDeviceUpdateMsgList() != null && !uplinkMsg.getDeviceUpdateMsgList().isEmpty()) {
  704 + for (DeviceUpdateMsg deviceUpdateMsg : uplinkMsg.getDeviceUpdateMsgList()) {
  705 + String deviceName = deviceUpdateMsg.getName();
  706 + String deviceType = deviceUpdateMsg.getType();
  707 + switch (deviceUpdateMsg.getMsgType()) {
  708 + case ENTITY_CREATED_RPC_MESSAGE:
  709 + getOrCreateDevice(deviceName, deviceType);
  710 + break;
  711 + }
  712 + }
  713 + }
  714 + if (uplinkMsg.getAlarmUpdatemsgList() != null && !uplinkMsg.getAlarmUpdatemsgList().isEmpty()) {
  715 + for (AlarmUpdateMsg alarmUpdateMsg : uplinkMsg.getAlarmUpdatemsgList()) {
  716 + onAlarmUpdate(alarmUpdateMsg);
  717 + }
  718 + }
  719 + } catch (Exception e) {
  720 + return UplinkResponseMsg.newBuilder().setSuccess(false).setErrorMsg(e.getMessage()).build();
  721 + }
  722 +
  723 + return UplinkResponseMsg.newBuilder().setSuccess(true).build();
  724 + }
  725 +
  726 + private EntityId getAlarmOriginator(String entityName, org.thingsboard.server.common.data.EntityType entityType) {
  727 + switch (entityType) {
  728 + case DEVICE:
  729 + return ctx.getDeviceService().findDeviceByTenantIdAndName(edge.getTenantId(), entityName).getId();
  730 + case ASSET:
  731 + return ctx.getAssetService().findAssetByTenantIdAndName(edge.getTenantId(), entityName).getId();
  732 + case ENTITY_VIEW:
  733 + return ctx.getEntityViewService().findEntityViewByTenantIdAndName(edge.getTenantId(), entityName).getId();
  734 + default:
  735 + return null;
  736 + }
  737 + }
  738 +
  739 + private void onAlarmUpdate(AlarmUpdateMsg alarmUpdateMsg) {
  740 + EntityId originatorId = getAlarmOriginator(alarmUpdateMsg.getOriginatorName(), org.thingsboard.server.common.data.EntityType.valueOf(alarmUpdateMsg.getOriginatorType()));
  741 + if (originatorId != null) {
  742 + try {
  743 + Alarm existentAlarm = ctx.getAlarmService().findLatestByOriginatorAndType(edge.getTenantId(), originatorId, alarmUpdateMsg.getType()).get();
  744 + switch (alarmUpdateMsg.getMsgType()) {
  745 + case ENTITY_CREATED_RPC_MESSAGE:
  746 + case ENTITY_UPDATED_RPC_MESSAGE:
  747 + if (existentAlarm == null) {
  748 + existentAlarm = new Alarm();
  749 + existentAlarm.setTenantId(edge.getTenantId());
  750 + existentAlarm.setType(alarmUpdateMsg.getName());
  751 + existentAlarm.setOriginator(originatorId);
  752 + existentAlarm.setSeverity(AlarmSeverity.valueOf(alarmUpdateMsg.getSeverity()));
  753 + existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus()));
  754 + existentAlarm.setStartTs(alarmUpdateMsg.getStartTs());
  755 + existentAlarm.setAckTs(alarmUpdateMsg.getAckTs());
  756 + existentAlarm.setClearTs(alarmUpdateMsg.getClearTs());
  757 + existentAlarm.setPropagate(alarmUpdateMsg.getPropagate());
  758 + }
  759 + existentAlarm.setEndTs(alarmUpdateMsg.getEndTs());
  760 + existentAlarm.setDetails(objectMapper.readTree(alarmUpdateMsg.getDetails()));
  761 + ctx.getAlarmService().createOrUpdateAlarm(existentAlarm);
  762 + break;
  763 + case ALARM_ACK_RPC_MESSAGE:
  764 + if (existentAlarm != null) {
  765 + ctx.getAlarmService().ackAlarm(edge.getTenantId(), existentAlarm.getId(), alarmUpdateMsg.getAckTs());
  766 + }
  767 + break;
  768 + case ALARM_CLEAR_RPC_MESSAGE:
  769 + if (existentAlarm != null) {
  770 + ctx.getAlarmService().clearAlarm(edge.getTenantId(), existentAlarm.getId(), objectMapper.readTree(alarmUpdateMsg.getDetails()), alarmUpdateMsg.getAckTs());
  771 + }
  772 + break;
  773 + case ENTITY_DELETED_RPC_MESSAGE:
  774 + if (existentAlarm != null) {
  775 + ctx.getAlarmService().deleteAlarm(edge.getTenantId(), existentAlarm.getId());
  776 + }
  777 + break;
  778 + }
  779 + } catch (Exception e) {
  780 + log.error("Error during finding existent alarm", e);
  781 + }
  782 + }
  783 + }
  784 +
  785 + private Device getOrCreateDevice(String deviceName, String deviceType) {
  786 + Device device = ctx.getDeviceService().findDeviceByTenantIdAndName(edge.getTenantId(), deviceName);
  787 + if (device == null) {
  788 + entityCreationLock.lock();
  789 + try {
  790 + return processGetOrCreateDevice(deviceName, deviceType);
  791 + } finally {
  792 + entityCreationLock.unlock();
  793 + }
  794 + }
  795 + return device;
  796 + }
  797 +
  798 + private Device processGetOrCreateDevice(String deviceName, String deviceType) {
  799 + Device device = ctx.getDeviceService().findDeviceByTenantIdAndName(edge.getTenantId(), deviceName);
  800 + if (device == null) {
  801 + device = new Device();
  802 + device.setName(deviceName);
  803 + device.setType(deviceType);
  804 + device.setTenantId(edge.getTenantId());
  805 + device.setCustomerId(edge.getCustomerId());
  806 + device = ctx.getDeviceService().saveDevice(device);
  807 + device = ctx.getDeviceService().assignDeviceToEdge(edge.getTenantId(), device.getId(), edge.getId());
  808 + createRelationFromEdge(device.getId());
  809 + ctx.getActorService().onDeviceAdded(device);
  810 + pushDeviceCreatedEventToRuleEngine(device);
  811 + }
  812 + return device;
  813 + }
  814 +
  815 + private void pushDeviceCreatedEventToRuleEngine(Device device) {
  816 + try {
  817 + ObjectNode entityNode = objectMapper.valueToTree(device);
  818 + TbMsg msg = new TbMsg(UUIDs.timeBased(), DataConstants.ENTITY_CREATED, device.getId(), deviceActionTbMsgMetaData(device), objectMapper.writeValueAsString(entityNode), null, null, 0L);
  819 + ctx.getActorService().onMsg(new SendToClusterMsg(device.getId(), new ServiceToRuleEngineMsg(edge.getTenantId(), msg)));
  820 + } catch (JsonProcessingException | IllegalArgumentException e) {
  821 + log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e);
  822 + }
  823 + }
  824 +
  825 + private TbMsgMetaData deviceActionTbMsgMetaData(Device device) {
  826 + TbMsgMetaData metaData = getTbMsgMetaData();
  827 + CustomerId customerId = device.getCustomerId();
  828 + if (customerId != null && !customerId.isNullUid()) {
  829 + metaData.putValue("customerId", customerId.toString());
  830 + }
  831 + return metaData;
  832 + }
  833 +
  834 + private TbMsgMetaData getTbMsgMetaData() {
  835 + TbMsgMetaData metaData = new TbMsgMetaData();
  836 + metaData.putValue("edgeId", edge.getId().toString());
  837 + metaData.putValue("edgeName", edge.getName());
  838 + return metaData;
  839 + }
  840 +
  841 + private ConnectResponseMsg processConnect(ConnectRequestMsg request) {
  842 + Optional<Edge> optional = ctx.getEdgeService().findEdgeByRoutingKey(TenantId.SYS_TENANT_ID, request.getEdgeRoutingKey());
  843 + if (optional.isPresent()) {
  844 + edge = optional.get();
  845 + try {
  846 + if (edge.getSecret().equals(request.getEdgeSecret())) {
  847 + connected = true;
  848 + sessionOpenListener.accept(edge.getId(), this);
  849 + return ConnectResponseMsg.newBuilder()
  850 + .setResponseCode(ConnectResponseCode.ACCEPTED)
  851 + .setErrorMsg("")
  852 + .setConfiguration(constructEdgeConfigProto(edge)).build();
  853 + }
  854 + return ConnectResponseMsg.newBuilder()
  855 + .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS)
  856 + .setErrorMsg("Failed to validate the edge!")
  857 + .setConfiguration(EdgeConfiguration.getDefaultInstance()).build();
  858 + } catch (Exception e) {
  859 + log.error("[{}] Failed to process edge connection!", request.getEdgeRoutingKey(), e);
  860 + return ConnectResponseMsg.newBuilder()
  861 + .setResponseCode(ConnectResponseCode.SERVER_UNAVAILABLE)
  862 + .setErrorMsg("Failed to process edge connection!")
  863 + .setConfiguration(EdgeConfiguration.getDefaultInstance()).build();
  864 + }
  865 + }
  866 + return ConnectResponseMsg.newBuilder()
  867 + .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS)
  868 + .setErrorMsg("Failed to find the edge! Routing key: " + request.getEdgeRoutingKey())
  869 + .setConfiguration(EdgeConfiguration.getDefaultInstance()).build();
  870 + }
  871 +
  872 + private void createRelationFromEdge(EntityId entityId) {
  873 + EntityRelation relation = new EntityRelation();
  874 + relation.setFrom(edge.getId());
  875 + relation.setTo(entityId);
  876 + relation.setTypeGroup(RelationTypeGroup.COMMON);
  877 + relation.setType(EntityRelation.EDGE_TYPE);
  878 + ctx.getRelationService().saveRelation(edge.getTenantId(), relation);
  879 + }
  880 +
  881 + private EdgeConfiguration constructEdgeConfigProto(Edge edge) throws JsonProcessingException {
  882 + return EdgeConfiguration.newBuilder()
  883 + .setTenantIdMSB(edge.getTenantId().getId().getMostSignificantBits())
  884 + .setTenantIdLSB(edge.getTenantId().getId().getLeastSignificantBits())
  885 + .setName(edge.getName())
  886 + .setRoutingKey(edge.getRoutingKey())
  887 + .setType(edge.getType().toString())
  888 + .build();
  889 + }
  890 +}
@@ -211,6 +211,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -211,6 +211,8 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
211 case "2.4.3": 211 case "2.4.3":
212 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { 212 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
213 log.info("Updating schema ..."); 213 log.info("Updating schema ...");
  214 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.5.0", SCHEMA_UPDATE_SQL);
  215 + loadSql(schemaUpdateFile, conn);
214 try { 216 try {
215 conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v json;"); 217 conn.createStatement().execute("ALTER TABLE attribute_kv ADD COLUMN json_v json;");
216 } catch (Exception e) { 218 } catch (Exception e) {
@@ -221,6 +223,24 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @@ -221,6 +223,24 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
221 } 223 }
222 } 224 }
223 } 225 }
  226 + try {
  227 + conn.createStatement().execute("ALTER TABLE asset ADD edge_id varchar(31)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  228 + } catch (Exception e) {}
  229 + try {
  230 + conn.createStatement().execute("ALTER TABLE device ADD edge_id varchar(31)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  231 + } catch (Exception e) {}
  232 + try {
  233 + conn.createStatement().execute("ALTER TABLE entity_view ADD edge_id varchar(31)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  234 + } catch (Exception e) {}
  235 + try {
  236 + conn.createStatement().execute("ALTER TABLE dashboard ADD assigned_edges varchar(1000000)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  237 + } catch (Exception e) {}
  238 + try {
  239 + conn.createStatement().execute("ALTER TABLE rule_chain ADD assigned_edges varchar(1000000)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  240 + } catch (Exception e) {}
  241 + try {
  242 + conn.createStatement().execute("ALTER TABLE rule_chain ADD type varchar(255) DEFAULT 'SYSTEM'"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  243 + } catch (Exception e) {}
224 log.info("Schema updated."); 244 log.info("Schema updated.");
225 } 245 }
226 break; 246 break;
@@ -30,10 +30,12 @@ import org.thingsboard.server.common.data.Device; @@ -30,10 +30,12 @@ import org.thingsboard.server.common.data.Device;
30 import org.thingsboard.server.common.data.EntityView; 30 import org.thingsboard.server.common.data.EntityView;
31 import org.thingsboard.server.common.data.Tenant; 31 import org.thingsboard.server.common.data.Tenant;
32 import org.thingsboard.server.common.data.asset.Asset; 32 import org.thingsboard.server.common.data.asset.Asset;
  33 +import org.thingsboard.server.common.data.edge.Edge;
33 import org.thingsboard.server.common.data.exception.ThingsboardException; 34 import org.thingsboard.server.common.data.exception.ThingsboardException;
34 import org.thingsboard.server.common.data.id.AssetId; 35 import org.thingsboard.server.common.data.id.AssetId;
35 import org.thingsboard.server.common.data.id.CustomerId; 36 import org.thingsboard.server.common.data.id.CustomerId;
36 import org.thingsboard.server.common.data.id.DeviceId; 37 import org.thingsboard.server.common.data.id.DeviceId;
  38 +import org.thingsboard.server.common.data.id.EdgeId;
37 import org.thingsboard.server.common.data.id.EntityId; 39 import org.thingsboard.server.common.data.id.EntityId;
38 import org.thingsboard.server.common.data.id.EntityIdFactory; 40 import org.thingsboard.server.common.data.id.EntityIdFactory;
39 import org.thingsboard.server.common.data.id.EntityViewId; 41 import org.thingsboard.server.common.data.id.EntityViewId;
@@ -47,6 +49,7 @@ import org.thingsboard.server.dao.alarm.AlarmService; @@ -47,6 +49,7 @@ import org.thingsboard.server.dao.alarm.AlarmService;
47 import org.thingsboard.server.dao.asset.AssetService; 49 import org.thingsboard.server.dao.asset.AssetService;
48 import org.thingsboard.server.dao.customer.CustomerService; 50 import org.thingsboard.server.dao.customer.CustomerService;
49 import org.thingsboard.server.dao.device.DeviceService; 51 import org.thingsboard.server.dao.device.DeviceService;
  52 +import org.thingsboard.server.dao.edge.EdgeService;
50 import org.thingsboard.server.dao.entityview.EntityViewService; 53 import org.thingsboard.server.dao.entityview.EntityViewService;
51 import org.thingsboard.server.dao.rule.RuleChainService; 54 import org.thingsboard.server.dao.rule.RuleChainService;
52 import org.thingsboard.server.dao.tenant.TenantService; 55 import org.thingsboard.server.dao.tenant.TenantService;
@@ -74,6 +77,7 @@ public class AccessValidator { @@ -74,6 +77,7 @@ public class AccessValidator {
74 public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; 77 public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!";
75 public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!"; 78 public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!";
76 public static final String ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND = "Entity-view with requested id wasn't found!"; 79 public static final String ENTITY_VIEW_WITH_REQUESTED_ID_NOT_FOUND = "Entity-view with requested id wasn't found!";
  80 + public static final String EDGE_WITH_REQUESTED_ID_NOT_FOUND = "Edge with requested id wasn't found!";
77 81
78 @Autowired 82 @Autowired
79 protected TenantService tenantService; 83 protected TenantService tenantService;
@@ -100,6 +104,9 @@ public class AccessValidator { @@ -100,6 +104,9 @@ public class AccessValidator {
100 protected EntityViewService entityViewService; 104 protected EntityViewService entityViewService;
101 105
102 @Autowired 106 @Autowired
  107 + protected EdgeService edgeService;
  108 +
  109 + @Autowired
103 protected AccessControlService accessControlService; 110 protected AccessControlService accessControlService;
104 111
105 private ExecutorService executor; 112 private ExecutorService executor;
@@ -175,6 +182,9 @@ public class AccessValidator { @@ -175,6 +182,9 @@ public class AccessValidator {
175 case ENTITY_VIEW: 182 case ENTITY_VIEW:
176 validateEntityView(currentUser, operation, entityId, callback); 183 validateEntityView(currentUser, operation, entityId, callback);
177 return; 184 return;
  185 + case EDGE:
  186 + validateEdge(currentUser, operation, entityId, callback);
  187 + return;
178 default: 188 default:
179 //TODO: add support of other entities 189 //TODO: add support of other entities
180 throw new IllegalStateException("Not Implemented!"); 190 throw new IllegalStateException("Not Implemented!");
@@ -328,6 +338,26 @@ public class AccessValidator { @@ -328,6 +338,26 @@ public class AccessValidator {
328 } 338 }
329 } 339 }
330 340
  341 + private void validateEdge(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback<ValidationResult> callback) {
  342 + if (currentUser.isSystemAdmin()) {
  343 + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  344 + } else {
  345 + ListenableFuture<Edge> edgeFuture = edgeService.findEdgeByIdAsync(currentUser.getTenantId(), new EdgeId(entityId.getId()));
  346 + Futures.addCallback(edgeFuture, getCallback(callback, edge -> {
  347 + if (edge == null) {
  348 + return ValidationResult.entityNotFound(EDGE_WITH_REQUESTED_ID_NOT_FOUND);
  349 + } else {
  350 + try {
  351 + accessControlService.checkPermission(currentUser, Resource.EDGE, operation, entityId, edge);
  352 + } catch (ThingsboardException e) {
  353 + return ValidationResult.accessDenied(e.getMessage());
  354 + }
  355 + return ValidationResult.ok(edge);
  356 + }
  357 + }), executor);
  358 + }
  359 + }
  360 +
331 private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) { 361 private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
332 return new FutureCallback<T>() { 362 return new FutureCallback<T>() {
333 @Override 363 @Override
@@ -37,6 +37,7 @@ public class CustomerUserPermissions extends AbstractPermissions { @@ -37,6 +37,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
37 put(Resource.CUSTOMER, customerPermissionChecker); 37 put(Resource.CUSTOMER, customerPermissionChecker);
38 put(Resource.DASHBOARD, customerDashboardPermissionChecker); 38 put(Resource.DASHBOARD, customerDashboardPermissionChecker);
39 put(Resource.ENTITY_VIEW, customerEntityPermissionChecker); 39 put(Resource.ENTITY_VIEW, customerEntityPermissionChecker);
  40 + put(Resource.EDGE, customerEntityPermissionChecker);
40 put(Resource.USER, userPermissionChecker); 41 put(Resource.USER, userPermissionChecker);
41 put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); 42 put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker);
42 put(Resource.WIDGET_TYPE, widgetsPermissionChecker); 43 put(Resource.WIDGET_TYPE, widgetsPermissionChecker);
@@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.permission; @@ -18,6 +18,7 @@ package org.thingsboard.server.service.security.permission;
18 public enum Operation { 18 public enum Operation {
19 19
20 ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL, 20 ALL, CREATE, READ, WRITE, DELETE, ASSIGN_TO_CUSTOMER, UNASSIGN_FROM_CUSTOMER, RPC_CALL,
21 - READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES 21 + READ_CREDENTIALS, WRITE_CREDENTIALS, READ_ATTRIBUTES, WRITE_ATTRIBUTES, READ_TELEMETRY, WRITE_TELEMETRY, CLAIM_DEVICES,
  22 + ASSIGN_TO_EDGE, UNASSIGN_FROM_EDGE
22 23
23 } 24 }
@@ -27,6 +27,7 @@ public enum Resource { @@ -27,6 +27,7 @@ public enum Resource {
27 CUSTOMER(EntityType.CUSTOMER), 27 CUSTOMER(EntityType.CUSTOMER),
28 DASHBOARD(EntityType.DASHBOARD), 28 DASHBOARD(EntityType.DASHBOARD),
29 ENTITY_VIEW(EntityType.ENTITY_VIEW), 29 ENTITY_VIEW(EntityType.ENTITY_VIEW),
  30 + EDGE(EntityType.EDGE),
30 TENANT(EntityType.TENANT), 31 TENANT(EntityType.TENANT),
31 RULE_CHAIN(EntityType.RULE_CHAIN), 32 RULE_CHAIN(EntityType.RULE_CHAIN),
32 USER(EntityType.USER), 33 USER(EntityType.USER),
@@ -37,6 +37,7 @@ public class TenantAdminPermissions extends AbstractPermissions { @@ -37,6 +37,7 @@ public class TenantAdminPermissions extends AbstractPermissions {
37 put(Resource.CUSTOMER, tenantEntityPermissionChecker); 37 put(Resource.CUSTOMER, tenantEntityPermissionChecker);
38 put(Resource.DASHBOARD, tenantEntityPermissionChecker); 38 put(Resource.DASHBOARD, tenantEntityPermissionChecker);
39 put(Resource.ENTITY_VIEW, tenantEntityPermissionChecker); 39 put(Resource.ENTITY_VIEW, tenantEntityPermissionChecker);
  40 + put(Resource.EDGE, tenantEntityPermissionChecker);
40 put(Resource.TENANT, tenantPermissionChecker); 41 put(Resource.TENANT, tenantPermissionChecker);
41 put(Resource.RULE_CHAIN, tenantEntityPermissionChecker); 42 put(Resource.RULE_CHAIN, tenantEntityPermissionChecker);
42 put(Resource.USER, userPermissionChecker); 43 put(Resource.USER, userPermissionChecker);
@@ -26,6 +26,7 @@ @@ -26,6 +26,7 @@
26 </appender> 26 </appender>
27 27
28 <logger name="org.thingsboard.server" level="INFO" /> 28 <logger name="org.thingsboard.server" level="INFO" />
  29 + <logger name="org.thingsboard.server.service.edge" level="TRACE" />
29 <logger name="akka" level="INFO" /> 30 <logger name="akka" level="INFO" />
30 31
31 <root level="INFO"> 32 <root level="INFO">
@@ -294,6 +294,9 @@ caffeine: @@ -294,6 +294,9 @@ caffeine:
294 securitySettings: 294 securitySettings:
295 timeToLiveInMinutes: 1440 295 timeToLiveInMinutes: 1440
296 maxSize: 1 296 maxSize: 1
  297 + edges:
  298 + timeToLiveInMinutes: 1440
  299 + maxSize: 100000
297 300
298 redis: 301 redis:
299 # standalone or cluster 302 # standalone or cluster
@@ -558,6 +561,17 @@ transport: @@ -558,6 +561,17 @@ transport:
558 bind_port: "${COAP_BIND_PORT:5683}" 561 bind_port: "${COAP_BIND_PORT:5683}"
559 timeout: "${COAP_TIMEOUT:10000}" 562 timeout: "${COAP_TIMEOUT:10000}"
560 563
  564 +# Edges parameters
  565 +edges:
  566 + rpc:
  567 + enabled: "${EDGES_RPC_ENABLED:true}"
  568 + port: "${EDGES_RPC_PORT:60061}"
  569 + ssl:
  570 + # Enable/disable SSL support
  571 + enabled: "${EDGES_RPC_SSL_ENABLED:false}"
  572 + cert: "${EDGES_RPC_SSL_CERT:certChainFile.pem}"
  573 + privateKey: "${EDGES_RPC_SSL_PRIVATE_KEY:privateKeyFile.pem}"
  574 +
561 swagger: 575 swagger:
562 api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" 576 api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}"
563 security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}" 577 security_path_regex: "${SWAGGER_SECURITY_PATH_REGEX:/api.*}"
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.core.type.TypeReference;
  20 +import org.apache.commons.lang3.RandomStringUtils;
  21 +import org.junit.After;
  22 +import org.junit.Assert;
  23 +import org.junit.Before;
  24 +import org.junit.Test;
  25 +import org.thingsboard.server.common.data.Customer;
  26 +import org.thingsboard.server.common.data.EntitySubtype;
  27 +import org.thingsboard.server.common.data.Tenant;
  28 +import org.thingsboard.server.common.data.User;
  29 +import org.thingsboard.server.common.data.edge.Edge;
  30 +import org.thingsboard.server.common.data.id.CustomerId;
  31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.PageLink;
  33 +import org.thingsboard.server.common.data.page.PageData;
  34 +import org.thingsboard.server.common.data.page.PageLink;
  35 +import org.thingsboard.server.common.data.security.Authority;
  36 +import org.thingsboard.server.dao.model.ModelConstants;
  37 +
  38 +import java.util.ArrayList;
  39 +import java.util.Collections;
  40 +import java.util.List;
  41 +
  42 +import static org.hamcrest.Matchers.containsString;
  43 +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
  44 +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
  45 +
  46 +public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
  47 +
  48 + private IdComparator<Edge> idComparator = new IdComparator<>();
  49 +
  50 + private Tenant savedTenant;
  51 + private User tenantAdmin;
  52 +
  53 + @Before
  54 + public void beforeTest() throws Exception {
  55 + loginSysAdmin();
  56 +
  57 + Tenant tenant = new Tenant();
  58 + tenant.setTitle("My tenant");
  59 + savedTenant = doPost("/api/tenant", tenant, Tenant.class);
  60 + Assert.assertNotNull(savedTenant);
  61 +
  62 + tenantAdmin = new User();
  63 + tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
  64 + tenantAdmin.setTenantId(savedTenant.getId());
  65 + tenantAdmin.setEmail("tenant2@thingsboard.org");
  66 + tenantAdmin.setFirstName("Joe");
  67 + tenantAdmin.setLastName("Downs");
  68 +
  69 + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
  70 + }
  71 +
  72 + @After
  73 + public void afterTest() throws Exception {
  74 + loginSysAdmin();
  75 +
  76 + doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
  77 + .andExpect(status().isOk());
  78 + }
  79 +
  80 + @Test
  81 + public void testSaveEdge() throws Exception {
  82 + Edge edge = new Edge();
  83 + edge.setName("My edge");
  84 + edge.setType("default");
  85 + Edge savedEdge = doPost("/api/edge", edge, Edge.class);
  86 +
  87 + Assert.assertNotNull(savedEdge);
  88 + Assert.assertNotNull(savedEdge.getId());
  89 + Assert.assertTrue(savedEdge.getCreatedTime() > 0);
  90 + Assert.assertEquals(savedTenant.getId(), savedEdge.getTenantId());
  91 + Assert.assertNotNull(savedEdge.getCustomerId());
  92 + Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId());
  93 + Assert.assertEquals(edge.getName(), savedEdge.getName());
  94 +
  95 + savedEdge.setName("My new edge");
  96 + doPost("/api/edge", savedEdge, Edge.class);
  97 +
  98 + Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  99 + Assert.assertEquals(foundEdge.getName(), savedEdge.getName());
  100 + }
  101 +
  102 + @Test
  103 + public void testFindEdgeById() throws Exception {
  104 + Edge edge = new Edge();
  105 + edge.setName("My edge");
  106 + edge.setType("default");
  107 + Edge savedEdge = doPost("/api/edge", edge, Edge.class);
  108 + Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  109 + Assert.assertNotNull(foundEdge);
  110 + Assert.assertEquals(savedEdge, foundEdge);
  111 + }
  112 +
  113 + @Test
  114 + public void testFindEdgeTypesByTenantId() throws Exception {
  115 + List<Edge> edges = new ArrayList<>();
  116 + for (int i = 0; i < 3; i++) {
  117 + Edge edge = new Edge();
  118 + edge.setName("My edge B" + i);
  119 + edge.setType("typeB");
  120 + edges.add(doPost("/api/edge", edge, Edge.class));
  121 + }
  122 + for (int i = 0; i < 7; i++) {
  123 + Edge edge = new Edge();
  124 + edge.setName("My edge C" + i);
  125 + edge.setType("typeC");
  126 + edges.add(doPost("/api/edge", edge, Edge.class));
  127 + }
  128 + for (int i = 0; i < 9; i++) {
  129 + Edge edge = new Edge();
  130 + edge.setName("My edge A" + i);
  131 + edge.setType("typeA");
  132 + edges.add(doPost("/api/edge", edge, Edge.class));
  133 + }
  134 + List<EntitySubtype> edgeTypes = doGetTyped("/api/edge/types",
  135 + new TypeReference<List<EntitySubtype>>() {
  136 + });
  137 +
  138 + Assert.assertNotNull(edgeTypes);
  139 + Assert.assertEquals(3, edgeTypes.size());
  140 + Assert.assertEquals("typeA", edgeTypes.get(0).getType());
  141 + Assert.assertEquals("typeB", edgeTypes.get(1).getType());
  142 + Assert.assertEquals("typeC", edgeTypes.get(2).getType());
  143 + }
  144 +
  145 + @Test
  146 + public void testDeleteEdge() throws Exception {
  147 + Edge edge = new Edge();
  148 + edge.setName("My edge");
  149 + edge.setType("default");
  150 + Edge savedEdge = doPost("/api/edge", edge, Edge.class);
  151 +
  152 + doDelete("/api/edge/" + savedEdge.getId().getId().toString())
  153 + .andExpect(status().isOk());
  154 +
  155 + doGet("/api/edge/" + savedEdge.getId().getId().toString())
  156 + .andExpect(status().isNotFound());
  157 + }
  158 +
  159 + @Test
  160 + public void testSaveEdgeWithEmptyType() throws Exception {
  161 + Edge edge = new Edge();
  162 + edge.setName("My edge");
  163 + doPost("/api/edge", edge)
  164 + .andExpect(status().isBadRequest())
  165 + .andExpect(statusReason(containsString("Edge type should be specified")));
  166 + }
  167 +
  168 + @Test
  169 + public void testSaveEdgeWithEmptyName() throws Exception {
  170 + Edge edge = new Edge();
  171 + edge.setType("default");
  172 + doPost("/api/edge", edge)
  173 + .andExpect(status().isBadRequest())
  174 + .andExpect(statusReason(containsString("Edge name should be specified")));
  175 + }
  176 +
  177 + @Test
  178 + public void testAssignUnassignEdgeToCustomer() throws Exception {
  179 + Edge edge = new Edge();
  180 + edge.setName("My edge");
  181 + edge.setType("default");
  182 + Edge savedEdge = doPost("/api/edge", edge, Edge.class);
  183 +
  184 + Customer customer = new Customer();
  185 + customer.setTitle("My customer");
  186 + Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
  187 +
  188 + Edge assignedEdge = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
  189 + + "/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  190 + Assert.assertEquals(savedCustomer.getId(), assignedEdge.getCustomerId());
  191 +
  192 + Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  193 + Assert.assertEquals(savedCustomer.getId(), foundEdge.getCustomerId());
  194 +
  195 + Edge unassignedEdge =
  196 + doDelete("/api/customer/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  197 + Assert.assertEquals(ModelConstants.NULL_UUID, unassignedEdge.getCustomerId().getId());
  198 +
  199 + foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
  200 + Assert.assertEquals(ModelConstants.NULL_UUID, foundEdge.getCustomerId().getId());
  201 + }
  202 +
  203 + @Test
  204 + public void testAssignEdgeToNonExistentCustomer() throws Exception {
  205 + Edge edge = new Edge();
  206 + edge.setName("My edge");
  207 + edge.setType("default");
  208 + Edge savedEdge = doPost("/api/edge", edge, Edge.class);
  209 +
  210 + doPost("/api/customer/" + UUIDs.timeBased().toString()
  211 + + "/edge/" + savedEdge.getId().getId().toString())
  212 + .andExpect(status().isNotFound());
  213 + }
  214 +
  215 + @Test
  216 + public void testAssignEdgeToCustomerFromDifferentTenant() throws Exception {
  217 + loginSysAdmin();
  218 +
  219 + Tenant tenant2 = new Tenant();
  220 + tenant2.setTitle("Different tenant");
  221 + Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
  222 + Assert.assertNotNull(savedTenant2);
  223 +
  224 + User tenantAdmin2 = new User();
  225 + tenantAdmin2.setAuthority(Authority.TENANT_ADMIN);
  226 + tenantAdmin2.setTenantId(savedTenant2.getId());
  227 + tenantAdmin2.setEmail("tenant3@thingsboard.org");
  228 + tenantAdmin2.setFirstName("Joe");
  229 + tenantAdmin2.setLastName("Downs");
  230 +
  231 + tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1");
  232 +
  233 + Customer customer = new Customer();
  234 + customer.setTitle("Different customer");
  235 + Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
  236 +
  237 + login(tenantAdmin.getEmail(), "testPassword1");
  238 +
  239 + Edge edge = new Edge();
  240 + edge.setName("My edge");
  241 + edge.setType("default");
  242 + Edge savedEdge = doPost("/api/edge", edge, Edge.class);
  243 +
  244 + doPost("/api/customer/" + savedCustomer.getId().getId().toString()
  245 + + "/edge/" + savedEdge.getId().getId().toString())
  246 + .andExpect(status().isForbidden());
  247 +
  248 + loginSysAdmin();
  249 +
  250 + doDelete("/api/tenant/" + savedTenant2.getId().getId().toString())
  251 + .andExpect(status().isOk());
  252 + }
  253 +
  254 + @Test
  255 + public void testFindTenantEdges() throws Exception {
  256 + List<Edge> edges = new ArrayList<>();
  257 + for (int i = 0; i < 178; i++) {
  258 + Edge edge = new Edge();
  259 + edge.setName("Edge" + i);
  260 + edge.setType("default");
  261 + edges.add(doPost("/api/edge", edge, Edge.class));
  262 + }
  263 + List<Edge> loadedEdges = new ArrayList<>();
  264 + PageLink pageLink = new PageLink(23);
  265 + PageData<Edge> pageData = null;
  266 + do {
  267 + pageData = doGetTypedWithPageLink("/api/tenant/edges?",
  268 + new TypeReference<PageData<Edge>>() {
  269 + }, pageLink);
  270 + loadedEdges.addAll(pageData.getData());
  271 + if (pageData.hasNext()) {
  272 + pageLink = pageLink.nextPageLink();
  273 + }
  274 + } while (pageData.hasNext());
  275 +
  276 + Collections.sort(edges, idComparator);
  277 + Collections.sort(loadedEdges, idComparator);
  278 +
  279 + Assert.assertEquals(edges, loadedEdges);
  280 + }
  281 +
  282 + @Test
  283 + public void testFindTenantEdgesByName() throws Exception {
  284 + String title1 = "Edge title 1";
  285 + List<Edge> edgesTitle1 = new ArrayList<>();
  286 + for (int i = 0; i < 143; i++) {
  287 + Edge edge = new Edge();
  288 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  289 + String name = title1 + suffix;
  290 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  291 + edge.setName(name);
  292 + edge.setType("default");
  293 + edgesTitle1.add(doPost("/api/edge", edge, Edge.class));
  294 + }
  295 + String title2 = "Edge title 2";
  296 + List<Edge> edgesTitle2 = new ArrayList<>();
  297 + for (int i = 0; i < 75; i++) {
  298 + Edge edge = new Edge();
  299 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  300 + String name = title2 + suffix;
  301 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  302 + edge.setName(name);
  303 + edge.setType("default");
  304 + edgesTitle2.add(doPost("/api/edge", edge, Edge.class));
  305 + }
  306 +
  307 + List<Edge> loadedEdgesTitle1 = new ArrayList<>();
  308 + PageLink pageLink = new PageLink(15, 0, title1);
  309 + PageData<Edge> pageData = null;
  310 + do {
  311 + pageData = doGetTypedWithPageLink("/api/tenant/edges?",
  312 + new TypeReference<PageData<Edge>>() {
  313 + }, pageLink);
  314 + loadedEdgesTitle1.addAll(pageData.getData());
  315 + if (pageData.hasNext()) {
  316 + pageLink = pageLink.nextPageLink();
  317 + }
  318 + } while (pageData.hasNext());
  319 +
  320 + Collections.sort(edgesTitle1, idComparator);
  321 + Collections.sort(loadedEdgesTitle1, idComparator);
  322 +
  323 + Assert.assertEquals(edgesTitle1, loadedEdgesTitle1);
  324 +
  325 + List<Edge> loadedEdgesTitle2 = new ArrayList<>();
  326 + pageLink = new PageLink(4, 0, title2);
  327 + do {
  328 + pageData = doGetTypedWithPageLink("/api/tenant/edges?",
  329 + new TypeReference<PageData<Edge>>() {
  330 + }, pageLink);
  331 + loadedEdgesTitle2.addAll(pageData.getData());
  332 + if (pageData.hasNext()) {
  333 + pageLink = pageLink.nextPageLink();
  334 + }
  335 + } while (pageData.hasNext());
  336 +
  337 + Collections.sort(edgesTitle2, idComparator);
  338 + Collections.sort(loadedEdgesTitle2, idComparator);
  339 +
  340 + Assert.assertEquals(edgesTitle2, loadedEdgesTitle2);
  341 +
  342 + for (Edge edge : loadedEdgesTitle1) {
  343 + doDelete("/api/edge/" + edge.getId().getId().toString())
  344 + .andExpect(status().isOk());
  345 + }
  346 +
  347 + pageLink = new PageLink(4, 0, title1);
  348 + pageData = doGetTypedWithPageLink("/api/tenant/edges?",
  349 + new TypeReference<PageData<Edge>>() {
  350 + }, pageLink);
  351 + Assert.assertFalse(pageData.hasNext());
  352 + Assert.assertEquals(0, pageData.getData().size());
  353 +
  354 + for (Edge edge : loadedEdgesTitle2) {
  355 + doDelete("/api/edge/" + edge.getId().getId().toString())
  356 + .andExpect(status().isOk());
  357 + }
  358 +
  359 + pageLink = new PageLink(4, 0, title2);
  360 + pageData = doGetTypedWithPageLink("/api/tenant/edges?",
  361 + new TypeReference<PageData<Edge>>() {
  362 + }, pageLink);
  363 + Assert.assertFalse(pageData.hasNext());
  364 + Assert.assertEquals(0, pageData.getData().size());
  365 + }
  366 +
  367 + @Test
  368 + public void testFindTenantEdgesByType() throws Exception {
  369 + String title1 = "Edge title 1";
  370 + String type1 = "typeA";
  371 + List<Edge> edgesType1 = new ArrayList<>();
  372 + for (int i = 0; i < 143; i++) {
  373 + Edge edge = new Edge();
  374 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  375 + String name = title1 + suffix;
  376 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  377 + edge.setName(name);
  378 + edge.setType(type1);
  379 + edgesType1.add(doPost("/api/edge", edge, Edge.class));
  380 + }
  381 + String title2 = "Edge title 2";
  382 + String type2 = "typeB";
  383 + List<Edge> edgesType2 = new ArrayList<>();
  384 + for (int i = 0; i < 75; i++) {
  385 + Edge edge = new Edge();
  386 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  387 + String name = title2 + suffix;
  388 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  389 + edge.setName(name);
  390 + edge.setType(type2);
  391 + edgesType2.add(doPost("/api/edge", edge, Edge.class));
  392 + }
  393 +
  394 + List<Edge> loadedEdgesType1 = new ArrayList<>();
  395 + PageLink pageLink = new PageLink(15);
  396 + PageData<Edge> pageData = null;
  397 + do {
  398 + pageData = doGetTypedWithPageLink("/api/tenant/edges?type={type}&",
  399 + new TypeReference<PageData<Edge>>() {
  400 + }, pageLink, type1);
  401 + loadedEdgesType1.addAll(pageData.getData());
  402 + if (pageData.hasNext()) {
  403 + pageLink = pageLink.nextPageLink();
  404 + }
  405 + } while (pageData.hasNext());
  406 +
  407 + Collections.sort(edgesType1, idComparator);
  408 + Collections.sort(loadedEdgesType1, idComparator);
  409 +
  410 + Assert.assertEquals(edgesType1, loadedEdgesType1);
  411 +
  412 + List<Edge> loadedEdgesType2 = new ArrayList<>();
  413 + pageLink = new PageLink(4);
  414 + do {
  415 + pageData = doGetTypedWithPageLink("/api/tenant/edges?type={type}&",
  416 + new TypeReference<PageData<Edge>>() {
  417 + }, pageLink, type2);
  418 + loadedEdgesType2.addAll(pageData.getData());
  419 + if (pageData.hasNext()) {
  420 + pageLink = pageLink.nextPageLink();
  421 + }
  422 + } while (pageData.hasNext());
  423 +
  424 + Collections.sort(edgesType2, idComparator);
  425 + Collections.sort(loadedEdgesType2, idComparator);
  426 +
  427 + Assert.assertEquals(edgesType2, loadedEdgesType2);
  428 +
  429 + for (Edge edge : loadedEdgesType1) {
  430 + doDelete("/api/edge/" + edge.getId().getId().toString())
  431 + .andExpect(status().isOk());
  432 + }
  433 +
  434 + pageLink = new PageLink(4);
  435 + pageData = doGetTypedWithPageLink("/api/tenant/edges?type={type}&",
  436 + new TypeReference<PageData<Edge>>() {
  437 + }, pageLink, type1);
  438 + Assert.assertFalse(pageData.hasNext());
  439 + Assert.assertEquals(0, pageData.getData().size());
  440 +
  441 + for (Edge edge : loadedEdgesType2) {
  442 + doDelete("/api/edge/" + edge.getId().getId().toString())
  443 + .andExpect(status().isOk());
  444 + }
  445 +
  446 + pageLink = new PageLink(4);
  447 + pageData = doGetTypedWithPageLink("/api/tenant/edges?type={type}&",
  448 + new TypeReference<PageData<Edge>>() {
  449 + }, pageLink, type2);
  450 + Assert.assertFalse(pageData.hasNext());
  451 + Assert.assertEquals(0, pageData.getData().size());
  452 + }
  453 +
  454 + @Test
  455 + public void testFindCustomerEdges() throws Exception {
  456 + Customer customer = new Customer();
  457 + customer.setTitle("Test customer");
  458 + customer = doPost("/api/customer", customer, Customer.class);
  459 + CustomerId customerId = customer.getId();
  460 +
  461 + List<Edge> edges = new ArrayList<>();
  462 + for (int i = 0; i < 128; i++) {
  463 + Edge edge = new Edge();
  464 + edge.setName("Edge" + i);
  465 + edge.setType("default");
  466 + edge = doPost("/api/edge", edge, Edge.class);
  467 + edges.add(doPost("/api/customer/" + customerId.getId().toString()
  468 + + "/edge/" + edge.getId().getId().toString(), Edge.class));
  469 + }
  470 +
  471 + List<Edge> loadedEdges = new ArrayList<>();
  472 + PageLink pageLink = new PageLink(23);
  473 + PageData<Edge> pageData = null;
  474 + do {
  475 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?",
  476 + new TypeReference<PageData<Edge>>() {
  477 + }, pageLink);
  478 + loadedEdges.addAll(pageData.getData());
  479 + if (pageData.hasNext()) {
  480 + pageLink = pageLink.nextPageLink();
  481 + }
  482 + } while (pageData.hasNext());
  483 +
  484 + Collections.sort(edges, idComparator);
  485 + Collections.sort(loadedEdges, idComparator);
  486 +
  487 + Assert.assertEquals(edges, loadedEdges);
  488 + }
  489 +
  490 + @Test
  491 + public void testFindCustomerEdgesByName() throws Exception {
  492 + Customer customer = new Customer();
  493 + customer.setTitle("Test customer");
  494 + customer = doPost("/api/customer", customer, Customer.class);
  495 + CustomerId customerId = customer.getId();
  496 +
  497 + String title1 = "Edge title 1";
  498 + List<Edge> edgesTitle1 = new ArrayList<>();
  499 + for (int i = 0; i < 125; i++) {
  500 + Edge edge = new Edge();
  501 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  502 + String name = title1 + suffix;
  503 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  504 + edge.setName(name);
  505 + edge.setType("default");
  506 + edge = doPost("/api/edge", edge, Edge.class);
  507 + edgesTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
  508 + + "/edge/" + edge.getId().getId().toString(), Edge.class));
  509 + }
  510 + String title2 = "Edge title 2";
  511 + List<Edge> edgesTitle2 = new ArrayList<>();
  512 + for (int i = 0; i < 143; i++) {
  513 + Edge edge = new Edge();
  514 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  515 + String name = title2 + suffix;
  516 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  517 + edge.setName(name);
  518 + edge.setType("default");
  519 + edge = doPost("/api/edge", edge, Edge.class);
  520 + edgesTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
  521 + + "/edge/" + edge.getId().getId().toString(), Edge.class));
  522 + }
  523 +
  524 + List<Edge> loadedEdgesTitle1 = new ArrayList<>();
  525 + PageLink pageLink = new PageLink(15,0, title1);
  526 + PageData<Edge> pageData = null;
  527 + do {
  528 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?",
  529 + new TypeReference<PageData<Edge>>() {
  530 + }, pageLink);
  531 + loadedEdgesTitle1.addAll(pageData.getData());
  532 + if (pageData.hasNext()) {
  533 + pageLink = pageLink.nextPageLink();
  534 + }
  535 + } while (pageData.hasNext());
  536 +
  537 + Collections.sort(edgesTitle1, idComparator);
  538 + Collections.sort(loadedEdgesTitle1, idComparator);
  539 +
  540 + Assert.assertEquals(edgesTitle1, loadedEdgesTitle1);
  541 +
  542 + List<Edge> loadedEdgesTitle2 = new ArrayList<>();
  543 + pageLink = new PageLink(4,0, title2);
  544 + do {
  545 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?",
  546 + new TypeReference<PageData<Edge>>() {
  547 + }, pageLink);
  548 + loadedEdgesTitle2.addAll(pageData.getData());
  549 + if (pageData.hasNext()) {
  550 + pageLink = pageLink.nextPageLink();
  551 + }
  552 + } while (pageData.hasNext());
  553 +
  554 + Collections.sort(edgesTitle2, idComparator);
  555 + Collections.sort(loadedEdgesTitle2, idComparator);
  556 +
  557 + Assert.assertEquals(edgesTitle2, loadedEdgesTitle2);
  558 +
  559 + for (Edge edge : loadedEdgesTitle1) {
  560 + doDelete("/api/customer/edge/" + edge.getId().getId().toString())
  561 + .andExpect(status().isOk());
  562 + }
  563 +
  564 + pageLink = new PageLink(4, 0, title1);
  565 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?",
  566 + new TypeReference<PageData<Edge>>() {
  567 + }, pageLink);
  568 + Assert.assertFalse(pageData.hasNext());
  569 + Assert.assertEquals(0, pageData.getData().size());
  570 +
  571 + for (Edge edge : loadedEdgesTitle2) {
  572 + doDelete("/api/customer/edge/" + edge.getId().getId().toString())
  573 + .andExpect(status().isOk());
  574 + }
  575 +
  576 + pageLink = new PageLink(4, 0, title2);
  577 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?",
  578 + new TypeReference<PageData<Edge>>() {
  579 + }, pageLink);
  580 + Assert.assertFalse(pageData.hasNext());
  581 + Assert.assertEquals(0, pageData.getData().size());
  582 + }
  583 +
  584 + @Test
  585 + public void testFindCustomerEdgesByType() throws Exception {
  586 + Customer customer = new Customer();
  587 + customer.setTitle("Test customer");
  588 + customer = doPost("/api/customer", customer, Customer.class);
  589 + CustomerId customerId = customer.getId();
  590 +
  591 + String title1 = "Edge title 1";
  592 + String type1 = "typeC";
  593 + List<Edge> edgesType1 = new ArrayList<>();
  594 + for (int i = 0; i < 125; i++) {
  595 + Edge edge = new Edge();
  596 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  597 + String name = title1 + suffix;
  598 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  599 + edge.setName(name);
  600 + edge.setType(type1);
  601 + edge = doPost("/api/edge", edge, Edge.class);
  602 + edgesType1.add(doPost("/api/customer/" + customerId.getId().toString()
  603 + + "/edge/" + edge.getId().getId().toString(), Edge.class));
  604 + }
  605 + String title2 = "Edge title 2";
  606 + String type2 = "typeD";
  607 + List<Edge> edgesType2 = new ArrayList<>();
  608 + for (int i = 0; i < 143; i++) {
  609 + Edge edge = new Edge();
  610 + String suffix = RandomStringUtils.randomAlphanumeric(15);
  611 + String name = title2 + suffix;
  612 + name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
  613 + edge.setName(name);
  614 + edge.setType(type2);
  615 + edge = doPost("/api/edge", edge, Edge.class);
  616 + edgesType2.add(doPost("/api/customer/" + customerId.getId().toString()
  617 + + "/edge/" + edge.getId().getId().toString(), Edge.class));
  618 + }
  619 +
  620 + List<Edge> loadedEdgesType1 = new ArrayList<>();
  621 + PageLink pageLink = new PageLink(15, 0, title1);
  622 + PageData<Edge> pageData = null;
  623 + do {
  624 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?type={type}&",
  625 + new TypeReference<PageData<Edge>>() {
  626 + }, pageLink, type1);
  627 + loadedEdgesType1.addAll(pageData.getData());
  628 + if (pageData.hasNext()) {
  629 + pageLink = pageLink.nextPageLink();
  630 + }
  631 + } while (pageData.hasNext());
  632 +
  633 + Collections.sort(edgesType1, idComparator);
  634 + Collections.sort(loadedEdgesType1, idComparator);
  635 +
  636 + Assert.assertEquals(edgesType1, loadedEdgesType1);
  637 +
  638 + List<Edge> loadedEdgesType2 = new ArrayList<>();
  639 + pageLink = new PageLink(4, 0, title2);
  640 + do {
  641 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?type={type}&",
  642 + new TypeReference<PageData<Edge>>() {
  643 + }, pageLink, type2);
  644 + loadedEdgesType2.addAll(pageData.getData());
  645 + if (pageData.hasNext()) {
  646 + pageLink = pageLink.nextPageLink();
  647 + }
  648 + } while (pageData.hasNext());
  649 +
  650 + Collections.sort(edgesType2, idComparator);
  651 + Collections.sort(loadedEdgesType2, idComparator);
  652 +
  653 + Assert.assertEquals(edgesType2, loadedEdgesType2);
  654 +
  655 + for (Edge edge : loadedEdgesType1) {
  656 + doDelete("/api/customer/edge/" + edge.getId().getId().toString())
  657 + .andExpect(status().isOk());
  658 + }
  659 +
  660 + pageLink = new PageLink(4, 0, title1);
  661 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?type={type}&",
  662 + new TypeReference<PageData<Edge>>() {
  663 + }, pageLink, type1);
  664 + Assert.assertFalse(pageData.hasNext());
  665 + Assert.assertEquals(0, pageData.getData().size());
  666 +
  667 + for (Edge edge : loadedEdgesType2) {
  668 + doDelete("/api/customer/edge/" + edge.getId().getId().toString())
  669 + .andExpect(status().isOk());
  670 + }
  671 +
  672 + pageLink = new PageLink(4, 0, title2);
  673 + pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/edges?type={type}&",
  674 + new TypeReference<PageData<Edge>>() {
  675 + }, pageLink, type2);
  676 + Assert.assertFalse(pageData.hasNext());
  677 + Assert.assertEquals(0, pageData.getData().size());
  678 + }
  679 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.controller.sql;
  17 +
  18 +import org.thingsboard.server.controller.BaseEdgeControllerTest;
  19 +import org.thingsboard.server.dao.service.DaoSqlTest;
  20 +
  21 +@DaoSqlTest
  22 +public class EdgeControllerSqlTest extends BaseEdgeControllerTest {
  23 +}
@@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.asset.AssetInfo; @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.asset.AssetInfo;
22 import org.thingsboard.server.common.data.asset.AssetSearchQuery; 22 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
23 import org.thingsboard.server.common.data.id.AssetId; 23 import org.thingsboard.server.common.data.id.AssetId;
24 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
  25 +import org.thingsboard.server.common.data.id.EdgeId;
25 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
26 import org.thingsboard.server.common.data.page.PageData; 27 import org.thingsboard.server.common.data.page.PageData;
27 import org.thingsboard.server.common.data.page.PageLink; 28 import org.thingsboard.server.common.data.page.PageLink;
@@ -74,4 +75,12 @@ public interface AssetService { @@ -74,4 +75,12 @@ public interface AssetService {
74 ListenableFuture<List<Asset>> findAssetsByQuery(TenantId tenantId, AssetSearchQuery query); 75 ListenableFuture<List<Asset>> findAssetsByQuery(TenantId tenantId, AssetSearchQuery query);
75 76
76 ListenableFuture<List<EntitySubtype>> findAssetTypesByTenantId(TenantId tenantId); 77 ListenableFuture<List<EntitySubtype>> findAssetTypesByTenantId(TenantId tenantId);
  78 +
  79 + Asset assignAssetToEdge(TenantId tenantId, AssetId assetId, EdgeId edgeId);
  80 +
  81 + Asset unassignAssetFromEdge(TenantId tenantId, AssetId assetId);
  82 +
  83 + PageData<Asset> findAssetsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
  84 +
  85 + PageData<Asset> findAssetsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink);
77 } 86 }
@@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.Dashboard; @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.Dashboard;
20 import org.thingsboard.server.common.data.DashboardInfo; 20 import org.thingsboard.server.common.data.DashboardInfo;
21 import org.thingsboard.server.common.data.id.CustomerId; 21 import org.thingsboard.server.common.data.id.CustomerId;
22 import org.thingsboard.server.common.data.id.DashboardId; 22 import org.thingsboard.server.common.data.id.DashboardId;
  23 +import org.thingsboard.server.common.data.id.EdgeId;
23 import org.thingsboard.server.common.data.id.TenantId; 24 import org.thingsboard.server.common.data.id.TenantId;
24 import org.thingsboard.server.common.data.page.PageData; 25 import org.thingsboard.server.common.data.page.PageData;
25 import org.thingsboard.server.common.data.page.PageLink; 26 import org.thingsboard.server.common.data.page.PageLink;
@@ -53,4 +54,13 @@ public interface DashboardService { @@ -53,4 +54,13 @@ public interface DashboardService {
53 54
54 void updateCustomerDashboards(TenantId tenantId, CustomerId customerId); 55 void updateCustomerDashboards(TenantId tenantId, CustomerId customerId);
55 56
  57 + Dashboard assignDashboardToEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId);
  58 +
  59 + Dashboard unassignDashboardFromEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId);
  60 +
  61 + void unassignEdgeDashboards(TenantId tenantId, EdgeId edgeId);
  62 +
  63 + void updateEdgeDashboards(TenantId tenantId, EdgeId edgeId);
  64 +
  65 + PageData<DashboardInfo> findDashboardsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
56 } 66 }
@@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntitySubtype; @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.EntitySubtype;
22 import org.thingsboard.server.common.data.device.DeviceSearchQuery; 22 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
23 import org.thingsboard.server.common.data.id.CustomerId; 23 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.DeviceId; 24 import org.thingsboard.server.common.data.id.DeviceId;
  25 +import org.thingsboard.server.common.data.id.EdgeId;
25 import org.thingsboard.server.common.data.id.TenantId; 26 import org.thingsboard.server.common.data.id.TenantId;
26 import org.thingsboard.server.common.data.page.PageData; 27 import org.thingsboard.server.common.data.page.PageData;
27 import org.thingsboard.server.common.data.page.PageLink; 28 import org.thingsboard.server.common.data.page.PageLink;
@@ -76,4 +77,11 @@ public interface DeviceService { @@ -76,4 +77,11 @@ public interface DeviceService {
76 77
77 ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId); 78 ListenableFuture<List<EntitySubtype>> findDeviceTypesByTenantId(TenantId tenantId);
78 79
  80 + Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId);
  81 +
  82 + Device unassignDeviceFromEdge(TenantId tenantId, DeviceId deviceId);
  83 +
  84 + PageData<Device> findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
  85 +
  86 + PageData<Device> findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink);
79 } 87 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.edge;
  17 +
  18 +import com.google.common.util.concurrent.FutureCallback;
  19 +import com.google.common.util.concurrent.ListenableFuture;
  20 +import org.thingsboard.server.common.data.EntitySubtype;
  21 +import org.thingsboard.server.common.data.Event;
  22 +import org.thingsboard.server.common.data.edge.Edge;
  23 +import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
  24 +import org.thingsboard.server.common.data.id.CustomerId;
  25 +import org.thingsboard.server.common.data.id.EdgeId;
  26 +import org.thingsboard.server.common.data.id.RuleChainId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.page.PageData;
  29 +import org.thingsboard.server.common.data.page.PageLink;
  30 +import org.thingsboard.server.common.data.page.TimePageLink;
  31 +import org.thingsboard.server.common.msg.TbMsg;
  32 +
  33 +import java.io.IOException;
  34 +import java.util.List;
  35 +import java.util.Optional;
  36 +
  37 +public interface EdgeService {
  38 +
  39 + Edge findEdgeById(TenantId tenantId, EdgeId edgeId);
  40 +
  41 + ListenableFuture<Edge> findEdgeByIdAsync(TenantId tenantId, EdgeId edgeId);
  42 +
  43 + Edge findEdgeByTenantIdAndName(TenantId tenantId, String name);
  44 +
  45 + Optional<Edge> findEdgeByRoutingKey(TenantId tenantId, String routingKey);
  46 +
  47 + Edge saveEdge(Edge edge);
  48 +
  49 + Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId);
  50 +
  51 + Edge unassignEdgeFromCustomer(TenantId tenantId, EdgeId edgeId);
  52 +
  53 + void deleteEdge(TenantId tenantId, EdgeId edgeId);
  54 +
  55 + PageData<Edge> findEdgesByTenantId(TenantId tenantId, PageLink pageLink);
  56 +
  57 + PageData<Edge> findEdgesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink);
  58 +
  59 + ListenableFuture<List<Edge>> findEdgesByTenantIdAndIdsAsync(TenantId tenantId, List<EdgeId> edgeIds);
  60 +
  61 + void deleteEdgesByTenantId(TenantId tenantId);
  62 +
  63 + PageData<Edge> findEdgesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink);
  64 +
  65 + PageData<Edge> findEdgesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink);
  66 +
  67 + ListenableFuture<List<Edge>> findEdgesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<EdgeId> edgeIds);
  68 +
  69 + void unassignCustomerEdges(TenantId tenantId, CustomerId customerId);
  70 +
  71 + ListenableFuture<List<Edge>> findEdgesByQuery(TenantId tenantId, EdgeSearchQuery query);
  72 +
  73 + ListenableFuture<List<EntitySubtype>> findEdgeTypesByTenantId(TenantId tenantId);
  74 +
  75 + void pushEventToEdge(TenantId tenantId, TbMsg tbMsg, FutureCallback<Void> callback);
  76 +
  77 + PageData<Event> findQueueEvents(TenantId tenantId, EdgeId edgeId, TimePageLink pageLink);
  78 +
  79 + Edge setRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException;
  80 +}
@@ -19,9 +19,9 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -19,9 +19,9 @@ import com.google.common.util.concurrent.ListenableFuture;
19 import org.thingsboard.server.common.data.EntitySubtype; 19 import org.thingsboard.server.common.data.EntitySubtype;
20 import org.thingsboard.server.common.data.EntityView; 20 import org.thingsboard.server.common.data.EntityView;
21 import org.thingsboard.server.common.data.EntityViewInfo; 21 import org.thingsboard.server.common.data.EntityViewInfo;
22 -import org.thingsboard.server.common.data.Tenant;  
23 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; 22 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
24 import org.thingsboard.server.common.data.id.CustomerId; 23 import org.thingsboard.server.common.data.id.CustomerId;
  24 +import org.thingsboard.server.common.data.id.EdgeId;
25 import org.thingsboard.server.common.data.id.EntityId; 25 import org.thingsboard.server.common.data.id.EntityId;
26 import org.thingsboard.server.common.data.id.EntityViewId; 26 import org.thingsboard.server.common.data.id.EntityViewId;
27 import org.thingsboard.server.common.data.id.TenantId; 27 import org.thingsboard.server.common.data.id.TenantId;
@@ -76,4 +76,12 @@ public interface EntityViewService { @@ -76,4 +76,12 @@ public interface EntityViewService {
76 void deleteEntityViewsByTenantId(TenantId tenantId); 76 void deleteEntityViewsByTenantId(TenantId tenantId);
77 77
78 ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId); 78 ListenableFuture<List<EntitySubtype>> findEntityViewTypesByTenantId(TenantId tenantId);
  79 +
  80 + EntityView assignEntityViewToEdge(TenantId tenantId, EntityViewId entityViewId, EdgeId edgeId);
  81 +
  82 + EntityView unassignEntityViewFromEdge(TenantId tenantId, EntityViewId entityViewId);
  83 +
  84 + PageData<EntityView> findEntityViewsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
  85 +
  86 + PageData<EntityView> findEntityViewsByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink);
79 } 87 }
@@ -16,14 +16,17 @@ @@ -16,14 +16,17 @@
16 package org.thingsboard.server.dao.rule; 16 package org.thingsboard.server.dao.rule;
17 17
18 import com.google.common.util.concurrent.ListenableFuture; 18 import com.google.common.util.concurrent.ListenableFuture;
  19 +import org.thingsboard.server.common.data.id.EdgeId;
19 import org.thingsboard.server.common.data.id.RuleChainId; 20 import org.thingsboard.server.common.data.id.RuleChainId;
20 import org.thingsboard.server.common.data.id.RuleNodeId; 21 import org.thingsboard.server.common.data.id.RuleNodeId;
21 import org.thingsboard.server.common.data.id.TenantId; 22 import org.thingsboard.server.common.data.id.TenantId;
22 import org.thingsboard.server.common.data.page.PageData; 23 import org.thingsboard.server.common.data.page.PageData;
23 import org.thingsboard.server.common.data.page.PageLink; 24 import org.thingsboard.server.common.data.page.PageLink;
  25 +import org.thingsboard.server.common.data.page.TimePageLink;
24 import org.thingsboard.server.common.data.relation.EntityRelation; 26 import org.thingsboard.server.common.data.relation.EntityRelation;
25 import org.thingsboard.server.common.data.rule.RuleChain; 27 import org.thingsboard.server.common.data.rule.RuleChain;
26 import org.thingsboard.server.common.data.rule.RuleChainMetaData; 28 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  29 +import org.thingsboard.server.common.data.rule.RuleChainType;
27 import org.thingsboard.server.common.data.rule.RuleNode; 30 import org.thingsboard.server.common.data.rule.RuleNode;
28 31
29 import java.util.List; 32 import java.util.List;
@@ -59,8 +62,19 @@ public interface RuleChainService { @@ -59,8 +62,19 @@ public interface RuleChainService {
59 62
60 PageData<RuleChain> findTenantRuleChains(TenantId tenantId, PageLink pageLink); 63 PageData<RuleChain> findTenantRuleChains(TenantId tenantId, PageLink pageLink);
61 64
  65 + PageData<RuleChain> findTenantRuleChainsByType(TenantId tenantId, RuleChainType type, PageLink pageLink);
  66 +
62 void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId); 67 void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId);
63 68
64 void deleteRuleChainsByTenantId(TenantId tenantId); 69 void deleteRuleChainsByTenantId(TenantId tenantId);
65 70
  71 + RuleChain assignRuleChainToEdge(TenantId tenantId, RuleChainId ruleChainId, EdgeId edgeId);
  72 +
  73 + RuleChain unassignRuleChainFromEdge(TenantId tenantId, RuleChainId ruleChainId, EdgeId edgeId);
  74 +
  75 + void unassignEdgeRuleChains(TenantId tenantId, EdgeId edgeId);
  76 +
  77 + void updateEdgeRuleChains(TenantId tenantId, EdgeId edgeId);
  78 +
  79 + PageData<RuleChain> findRuleChainsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
66 } 80 }
@@ -22,6 +22,7 @@ public class CacheConstants { @@ -22,6 +22,7 @@ public class CacheConstants {
22 public static final String SESSIONS_CACHE = "sessions"; 22 public static final String SESSIONS_CACHE = "sessions";
23 public static final String ASSET_CACHE = "assets"; 23 public static final String ASSET_CACHE = "assets";
24 public static final String ENTITY_VIEW_CACHE = "entityViews"; 24 public static final String ENTITY_VIEW_CACHE = "entityViews";
  25 + public static final String EDGE_CACHE = "edges";
25 public static final String CLAIM_DEVICES_CACHE = "claimDevices"; 26 public static final String CLAIM_DEVICES_CACHE = "claimDevices";
26 public static final String SECURITY_SETTINGS_CACHE = "securitySettings"; 27 public static final String SECURITY_SETTINGS_CACHE = "securitySettings";
27 } 28 }
@@ -16,8 +16,12 @@ @@ -16,8 +16,12 @@
16 package org.thingsboard.server.common.data; 16 package org.thingsboard.server.common.data;
17 17
18 import com.fasterxml.jackson.annotation.JsonProperty; 18 import com.fasterxml.jackson.annotation.JsonProperty;
  19 +import lombok.Getter;
  20 +import lombok.Setter;
  21 +import org.thingsboard.server.common.data.edge.Edge;
19 import org.thingsboard.server.common.data.id.CustomerId; 22 import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.DashboardId; 23 import org.thingsboard.server.common.data.id.DashboardId;
  24 +import org.thingsboard.server.common.data.id.EdgeId;
21 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
22 26
23 import java.util.*; 27 import java.util.*;
@@ -28,6 +32,9 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -28,6 +32,9 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
28 private String title; 32 private String title;
29 private Set<ShortCustomerInfo> assignedCustomers; 33 private Set<ShortCustomerInfo> assignedCustomers;
30 34
  35 + @Getter @Setter
  36 + private Set<ShortEdgeInfo> assignedEdges;
  37 +
31 public DashboardInfo() { 38 public DashboardInfo() {
32 super(); 39 super();
33 } 40 }
@@ -116,6 +123,55 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa @@ -116,6 +123,55 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
116 } 123 }
117 } 124 }
118 125
  126 + public boolean isAssignedToEdge(EdgeId edgeId) {
  127 + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null, null));
  128 + }
  129 +
  130 + public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) {
  131 + if (this.assignedEdges != null) {
  132 + for (ShortEdgeInfo edgeInfo : this.assignedEdges) {
  133 + if (edgeInfo.getEdgeId().equals(edgeId)) {
  134 + return edgeInfo;
  135 + }
  136 + }
  137 + }
  138 + return null;
  139 + }
  140 +
  141 + public boolean addAssignedEdge(Edge edge) {
  142 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  143 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  144 + return false;
  145 + } else {
  146 + if (this.assignedEdges == null) {
  147 + this.assignedEdges = new HashSet<>();
  148 + }
  149 + this.assignedEdges.add(edgeInfo);
  150 + return true;
  151 + }
  152 + }
  153 +
  154 + public boolean updateAssignedEdge(Edge edge) {
  155 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  156 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  157 + this.assignedEdges.remove(edgeInfo);
  158 + this.assignedEdges.add(edgeInfo);
  159 + return true;
  160 + } else {
  161 + return false;
  162 + }
  163 + }
  164 +
  165 + public boolean removeAssignedEdge(Edge edge) {
  166 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  167 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  168 + this.assignedEdges.remove(edgeInfo);
  169 + return true;
  170 + } else {
  171 + return false;
  172 + }
  173 + }
  174 +
119 @Override 175 @Override
120 @JsonProperty(access = JsonProperty.Access.READ_ONLY) 176 @JsonProperty(access = JsonProperty.Access.READ_ONLY)
121 public String getName() { 177 public String getName() {
@@ -57,6 +57,8 @@ public class DataConstants { @@ -57,6 +57,8 @@ public class DataConstants {
57 public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED"; 57 public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED";
58 public static final String ALARM_ACK = "ALARM_ACK"; 58 public static final String ALARM_ACK = "ALARM_ACK";
59 public static final String ALARM_CLEAR = "ALARM_CLEAR"; 59 public static final String ALARM_CLEAR = "ALARM_CLEAR";
  60 + public static final String ENTITY_ASSIGNED_TO_EDGE = "ENTITY_ASSIGNED_TO_EDGE";
  61 + public static final String ENTITY_UNASSIGNED_FROM_EDGE = "ENTITY_UNASSIGNED_FROM_EDGE";
60 62
61 public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; 63 public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE";
62 64
@@ -64,4 +66,5 @@ public class DataConstants { @@ -64,4 +66,5 @@ public class DataConstants {
64 public static final String SECRET_KEY_FIELD_NAME = "secretKey"; 66 public static final String SECRET_KEY_FIELD_NAME = "secretKey";
65 public static final String DURATION_MS_FIELD_NAME = "durationMs"; 67 public static final String DURATION_MS_FIELD_NAME = "durationMs";
66 68
  69 + public static final String EDGE_QUEUE_EVENT_TYPE = "EDGE_QUEUE";
67 } 70 }
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data;
18 import lombok.EqualsAndHashCode; 18 import lombok.EqualsAndHashCode;
19 import org.thingsboard.server.common.data.id.CustomerId; 19 import org.thingsboard.server.common.data.id.CustomerId;
20 import org.thingsboard.server.common.data.id.DeviceId; 20 import org.thingsboard.server.common.data.id.DeviceId;
  21 +import org.thingsboard.server.common.data.id.EdgeId;
21 import org.thingsboard.server.common.data.id.TenantId; 22 import org.thingsboard.server.common.data.id.TenantId;
22 23
23 @EqualsAndHashCode(callSuper = true) 24 @EqualsAndHashCode(callSuper = true)
@@ -27,6 +28,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -27,6 +28,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
27 28
28 private TenantId tenantId; 29 private TenantId tenantId;
29 private CustomerId customerId; 30 private CustomerId customerId;
  31 + private EdgeId edgeId;
30 private String name; 32 private String name;
31 private String type; 33 private String type;
32 private String label; 34 private String label;
@@ -46,6 +48,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -46,6 +48,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
46 this.name = device.getName(); 48 this.name = device.getName();
47 this.type = device.getType(); 49 this.type = device.getType();
48 this.label = device.getLabel(); 50 this.label = device.getLabel();
  51 + this.edgeId = device.getEdgeId();
49 } 52 }
50 53
51 public TenantId getTenantId() { 54 public TenantId getTenantId() {
@@ -64,6 +67,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -64,6 +67,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
64 this.customerId = customerId; 67 this.customerId = customerId;
65 } 68 }
66 69
  70 + public EdgeId getEdgeId() {
  71 + return edgeId;
  72 + }
  73 +
  74 + public void setEdgeId(EdgeId edgeId) {
  75 + this.edgeId = edgeId;
  76 + }
  77 +
67 @Override 78 @Override
68 public String getName() { 79 public String getName() {
69 return name; 80 return name;
@@ -101,6 +112,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen @@ -101,6 +112,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
101 builder.append(tenantId); 112 builder.append(tenantId);
102 builder.append(", customerId="); 113 builder.append(", customerId=");
103 builder.append(customerId); 114 builder.append(customerId);
  115 + builder.append(", edgeId=");
  116 + builder.append(edgeId);
104 builder.append(", name="); 117 builder.append(", name=");
105 builder.append(name); 118 builder.append(name);
106 builder.append(", type="); 119 builder.append(", type=");
@@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data;
19 * @author Andrew Shvayka 19 * @author Andrew Shvayka
20 */ 20 */
21 public enum EntityType { 21 public enum EntityType {
22 - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE 22 + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, EDGE
23 } 23 }
@@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor;
19 import lombok.Data; 19 import lombok.Data;
20 import lombok.EqualsAndHashCode; 20 import lombok.EqualsAndHashCode;
21 import org.thingsboard.server.common.data.id.CustomerId; 21 import org.thingsboard.server.common.data.id.CustomerId;
  22 +import org.thingsboard.server.common.data.id.EdgeId;
22 import org.thingsboard.server.common.data.id.EntityId; 23 import org.thingsboard.server.common.data.id.EntityId;
23 import org.thingsboard.server.common.data.id.EntityViewId; 24 import org.thingsboard.server.common.data.id.EntityViewId;
24 import org.thingsboard.server.common.data.id.TenantId; 25 import org.thingsboard.server.common.data.id.TenantId;
@@ -39,6 +40,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId> @@ -39,6 +40,7 @@ public class EntityView extends SearchTextBasedWithAdditionalInfo<EntityViewId>
39 private EntityId entityId; 40 private EntityId entityId;
40 private TenantId tenantId; 41 private TenantId tenantId;
41 private CustomerId customerId; 42 private CustomerId customerId;
  43 + private EdgeId edgeId;
42 private String name; 44 private String name;
43 private String type; 45 private String type;
44 private TelemetryEntityView keys; 46 private TelemetryEntityView keys;
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Getter;
  20 +import lombok.Setter;
  21 +import org.thingsboard.server.common.data.id.EdgeId;
  22 +import org.thingsboard.server.common.data.id.RuleChainId;
  23 +
  24 +@AllArgsConstructor
  25 +public class ShortEdgeInfo {
  26 +
  27 + @Getter @Setter
  28 + private EdgeId edgeId;
  29 +
  30 + @Getter @Setter
  31 + private String title;
  32 +
  33 + @Getter @Setter
  34 + private RuleChainId rootRuleChainId;
  35 +
  36 + @Override
  37 + public boolean equals(Object o) {
  38 + if (this == o) return true;
  39 + if (o == null || getClass() != o.getClass()) return false;
  40 +
  41 + ShortEdgeInfo that = (ShortEdgeInfo) o;
  42 +
  43 + return edgeId.equals(that.edgeId);
  44 + }
  45 +
  46 + @Override
  47 + public int hashCode() {
  48 + return edgeId.hashCode();
  49 + }
  50 +}
@@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode; @@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode;
20 import org.thingsboard.server.common.data.*; 20 import org.thingsboard.server.common.data.*;
21 import org.thingsboard.server.common.data.id.AssetId; 21 import org.thingsboard.server.common.data.id.AssetId;
22 import org.thingsboard.server.common.data.id.CustomerId; 22 import org.thingsboard.server.common.data.id.CustomerId;
  23 +import org.thingsboard.server.common.data.id.EdgeId;
23 import org.thingsboard.server.common.data.id.TenantId; 24 import org.thingsboard.server.common.data.id.TenantId;
24 25
25 @EqualsAndHashCode(callSuper = true) 26 @EqualsAndHashCode(callSuper = true)
@@ -29,6 +30,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements @@ -29,6 +30,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
29 30
30 private TenantId tenantId; 31 private TenantId tenantId;
31 private CustomerId customerId; 32 private CustomerId customerId;
  33 + private EdgeId edgeId;
32 private String name; 34 private String name;
33 private String type; 35 private String type;
34 private String label; 36 private String label;
@@ -45,6 +47,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements @@ -45,6 +47,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
45 super(asset); 47 super(asset);
46 this.tenantId = asset.getTenantId(); 48 this.tenantId = asset.getTenantId();
47 this.customerId = asset.getCustomerId(); 49 this.customerId = asset.getCustomerId();
  50 + this.edgeId = asset.getEdgeId();
48 this.name = asset.getName(); 51 this.name = asset.getName();
49 this.type = asset.getType(); 52 this.type = asset.getType();
50 this.label = asset.getLabel(); 53 this.label = asset.getLabel();
@@ -66,6 +69,14 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements @@ -66,6 +69,14 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
66 this.customerId = customerId; 69 this.customerId = customerId;
67 } 70 }
68 71
  72 + public EdgeId getEdgeId() {
  73 + return edgeId;
  74 + }
  75 +
  76 + public void setEdgeId(EdgeId edgeId) {
  77 + this.edgeId = edgeId;
  78 + }
  79 +
69 @Override 80 @Override
70 public String getName() { 81 public String getName() {
71 return name; 82 return name;
@@ -103,6 +114,8 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements @@ -103,6 +114,8 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
103 builder.append(tenantId); 114 builder.append(tenantId);
104 builder.append(", customerId="); 115 builder.append(", customerId=");
105 builder.append(customerId); 116 builder.append(customerId);
  117 + builder.append(", edgeId=");
  118 + builder.append(edgeId);
106 builder.append(", name="); 119 builder.append(", name=");
107 builder.append(name); 120 builder.append(name);
108 builder.append(", type="); 121 builder.append(", type=");
@@ -40,7 +40,9 @@ public enum ActionType { @@ -40,7 +40,9 @@ public enum ActionType {
40 ALARM_CLEAR(false), 40 ALARM_CLEAR(false),
41 LOGIN(false), 41 LOGIN(false),
42 LOGOUT(false), 42 LOGOUT(false),
43 - LOCKOUT(false); 43 + LOCKOUT(false),
  44 + ASSIGNED_TO_EDGE(false), // log edge name
  45 + UNASSIGNED_FROM_EDGE(false); // log edge name
44 46
45 private final boolean isRead; 47 private final boolean isRead;
46 48
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.edge;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonIgnore;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import lombok.EqualsAndHashCode;
  21 +import lombok.Getter;
  22 +import lombok.Setter;
  23 +import lombok.ToString;
  24 +import org.thingsboard.server.common.data.HasCustomerId;
  25 +import org.thingsboard.server.common.data.HasName;
  26 +import org.thingsboard.server.common.data.HasTenantId;
  27 +import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  28 +import org.thingsboard.server.common.data.ShortCustomerInfo;
  29 +import org.thingsboard.server.common.data.ShortEdgeInfo;
  30 +import org.thingsboard.server.common.data.id.CustomerId;
  31 +import org.thingsboard.server.common.data.id.EdgeId;
  32 +import org.thingsboard.server.common.data.id.RuleChainId;
  33 +import org.thingsboard.server.common.data.id.TenantId;
  34 +
  35 +@EqualsAndHashCode(callSuper = true)
  36 +@ToString
  37 +@Getter
  38 +@Setter
  39 +public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements HasName, HasTenantId, HasCustomerId {
  40 +
  41 + private static final long serialVersionUID = 4934987555236873728L;
  42 +
  43 + private TenantId tenantId;
  44 + private CustomerId customerId;
  45 + private RuleChainId rootRuleChainId;
  46 + private String name;
  47 + private String type;
  48 + private String label;
  49 + private String routingKey;
  50 + private String secret;
  51 + private transient JsonNode configuration;
  52 +
  53 + public Edge() {
  54 + super();
  55 + }
  56 +
  57 + public Edge(EdgeId id) {
  58 + super(id);
  59 + }
  60 +
  61 + public Edge(Edge edge) {
  62 + super(edge);
  63 + this.tenantId = edge.getTenantId();
  64 + this.customerId = edge.getCustomerId();
  65 + this.rootRuleChainId = edge.getRootRuleChainId();
  66 + this.type = edge.getType();
  67 + this.name = edge.getName();
  68 + this.routingKey = edge.getRoutingKey();
  69 + this.secret = edge.getSecret();
  70 + this.configuration = edge.getConfiguration();
  71 + }
  72 +
  73 + @JsonIgnore
  74 + public ShortEdgeInfo toShortEdgeInfo() {
  75 + return new ShortEdgeInfo(id, name, rootRuleChainId);
  76 + }
  77 +
  78 + @Override
  79 + public String getSearchText() {
  80 + return getName();
  81 + }
  82 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.edge;
  17 +
  18 +public enum EdgeQueueEntityType {
  19 + DASHBOARD, ASSET, DEVICE, ENTITY_VIEW, ALARM, RULE_CHAIN, RULE_CHAIN_METADATA, EDGE, USER, CUSTOMER
  20 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.edge;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class EdgeQueueEntry {
  22 + private String type;
  23 + private EdgeQueueEntityType entityType;
  24 + private String data;
  25 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.edge;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.EntityType;
  20 +import org.thingsboard.server.common.data.relation.EntityRelation;
  21 +import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
  22 +import org.thingsboard.server.common.data.relation.EntityTypeFilter;
  23 +import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
  24 +
  25 +import java.util.Collections;
  26 +import java.util.List;
  27 +
  28 +@Data
  29 +public class EdgeSearchQuery {
  30 +
  31 + private RelationsSearchParameters parameters;
  32 + private String relationType;
  33 + private List<String> edgeTypes;
  34 +
  35 + public EntityRelationsQuery toEntitySearchQuery() {
  36 + EntityRelationsQuery query = new EntityRelationsQuery();
  37 + query.setParameters(parameters);
  38 + query.setFilters(
  39 + Collections.singletonList(new EntityTypeFilter(relationType == null ? EntityRelation.CONTAINS_TYPE : relationType,
  40 + Collections.singletonList(EntityType.EDGE))));
  41 + return query;
  42 + }
  43 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.id;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonIgnore;
  20 +import com.fasterxml.jackson.annotation.JsonProperty;
  21 +import org.thingsboard.server.common.data.EntityType;
  22 +
  23 +import java.util.UUID;
  24 +
  25 +public class EdgeId extends UUIDBased implements EntityId {
  26 +
  27 + private static final long serialVersionUID = 1L;
  28 +
  29 + @JsonCreator
  30 + public EdgeId(@JsonProperty("id") UUID id) {
  31 + super(id);
  32 + }
  33 +
  34 + public static EdgeId fromString(String integrationId) {
  35 + return new EdgeId(UUID.fromString(integrationId));
  36 + }
  37 +
  38 + @JsonIgnore
  39 + @Override
  40 + public EntityType getEntityType() {
  41 + return EntityType.EDGE;
  42 + }
  43 +}
@@ -63,6 +63,8 @@ public class EntityIdFactory { @@ -63,6 +63,8 @@ public class EntityIdFactory {
63 return new WidgetsBundleId(uuid); 63 return new WidgetsBundleId(uuid);
64 case WIDGET_TYPE: 64 case WIDGET_TYPE:
65 return new WidgetTypeId(uuid); 65 return new WidgetTypeId(uuid);
  66 + case EDGE:
  67 + return new EdgeId(uuid);
66 } 68 }
67 throw new IllegalArgumentException("EntityType " + type + " is not supported!"); 69 throw new IllegalArgumentException("EntityType " + type + " is not supported!");
68 } 70 }
@@ -32,6 +32,7 @@ public class EntityRelation implements Serializable { @@ -32,6 +32,7 @@ public class EntityRelation implements Serializable {
32 32
33 private static final long serialVersionUID = 2807343040519543363L; 33 private static final long serialVersionUID = 2807343040519543363L;
34 34
  35 + public static final String EDGE_TYPE = "ManagedByEdge";
35 public static final String CONTAINS_TYPE = "Contains"; 36 public static final String CONTAINS_TYPE = "Contains";
36 public static final String MANAGES_TYPE = "Manages"; 37 public static final String MANAGES_TYPE = "Manages";
37 38
@@ -21,6 +21,7 @@ public enum RelationTypeGroup { @@ -21,6 +21,7 @@ public enum RelationTypeGroup {
21 ALARM, 21 ALARM,
22 DASHBOARD, 22 DASHBOARD,
23 RULE_CHAIN, 23 RULE_CHAIN,
24 - RULE_NODE 24 + RULE_NODE,
  25 + EDGE
25 26
26 } 27 }
@@ -23,10 +23,16 @@ import lombok.extern.slf4j.Slf4j; @@ -23,10 +23,16 @@ import lombok.extern.slf4j.Slf4j;
23 import org.thingsboard.server.common.data.HasName; 23 import org.thingsboard.server.common.data.HasName;
24 import org.thingsboard.server.common.data.HasTenantId; 24 import org.thingsboard.server.common.data.HasTenantId;
25 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; 25 import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo;
  26 +import org.thingsboard.server.common.data.ShortEdgeInfo;
  27 +import org.thingsboard.server.common.data.edge.Edge;
  28 +import org.thingsboard.server.common.data.id.EdgeId;
26 import org.thingsboard.server.common.data.id.RuleChainId; 29 import org.thingsboard.server.common.data.id.RuleChainId;
27 import org.thingsboard.server.common.data.id.RuleNodeId; 30 import org.thingsboard.server.common.data.id.RuleNodeId;
28 import org.thingsboard.server.common.data.id.TenantId; 31 import org.thingsboard.server.common.data.id.TenantId;
29 32
  33 +import java.util.HashSet;
  34 +import java.util.Set;
  35 +
30 @Data 36 @Data
31 @EqualsAndHashCode(callSuper = true) 37 @EqualsAndHashCode(callSuper = true)
32 @Slf4j 38 @Slf4j
@@ -36,13 +42,16 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im @@ -36,13 +42,16 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
36 42
37 private TenantId tenantId; 43 private TenantId tenantId;
38 private String name; 44 private String name;
  45 + private RuleChainType type;
39 private RuleNodeId firstRuleNodeId; 46 private RuleNodeId firstRuleNodeId;
40 private boolean root; 47 private boolean root;
41 private boolean debugMode; 48 private boolean debugMode;
42 private transient JsonNode configuration; 49 private transient JsonNode configuration;
  50 + private Set<ShortEdgeInfo> assignedEdges;
43 @JsonIgnore 51 @JsonIgnore
44 private byte[] configurationBytes; 52 private byte[] configurationBytes;
45 53
  54 +
46 public RuleChain() { 55 public RuleChain() {
47 super(); 56 super();
48 } 57 }
@@ -55,8 +64,10 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im @@ -55,8 +64,10 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
55 super(ruleChain); 64 super(ruleChain);
56 this.tenantId = ruleChain.getTenantId(); 65 this.tenantId = ruleChain.getTenantId();
57 this.name = ruleChain.getName(); 66 this.name = ruleChain.getName();
  67 + this.type = ruleChain.getType();
58 this.firstRuleNodeId = ruleChain.getFirstRuleNodeId(); 68 this.firstRuleNodeId = ruleChain.getFirstRuleNodeId();
59 this.root = ruleChain.isRoot(); 69 this.root = ruleChain.isRoot();
  70 + this.assignedEdges = ruleChain.getAssignedEdges();
60 this.setConfiguration(ruleChain.getConfiguration()); 71 this.setConfiguration(ruleChain.getConfiguration());
61 } 72 }
62 73
@@ -78,4 +89,52 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im @@ -78,4 +89,52 @@ public class RuleChain extends SearchTextBasedWithAdditionalInfo<RuleChainId> im
78 setJson(data, json -> this.configuration = json, bytes -> this.configurationBytes = bytes); 89 setJson(data, json -> this.configuration = json, bytes -> this.configurationBytes = bytes);
79 } 90 }
80 91
  92 + public boolean isAssignedToEdge(EdgeId edgeId) {
  93 + return this.assignedEdges != null && this.assignedEdges.contains(new ShortEdgeInfo(edgeId, null, null));
  94 + }
  95 +
  96 + public ShortEdgeInfo getAssignedEdgeInfo(EdgeId edgeId) {
  97 + if (this.assignedEdges != null) {
  98 + for (ShortEdgeInfo edgeInfo : this.assignedEdges) {
  99 + if (edgeInfo.getEdgeId().equals(edgeId)) {
  100 + return edgeInfo;
  101 + }
  102 + }
  103 + }
  104 + return null;
  105 + }
  106 +
  107 + public boolean addAssignedEdge(Edge edge) {
  108 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  109 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  110 + return false;
  111 + } else {
  112 + if (this.assignedEdges == null) {
  113 + this.assignedEdges = new HashSet<>();
  114 + }
  115 + this.assignedEdges.add(edgeInfo);
  116 + return true;
  117 + }
  118 + }
  119 +
  120 + public boolean updateAssignedEdge(Edge edge) {
  121 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  122 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  123 + this.assignedEdges.remove(edgeInfo);
  124 + this.assignedEdges.add(edgeInfo);
  125 + return true;
  126 + } else {
  127 + return false;
  128 + }
  129 + }
  130 +
  131 + public boolean removeAssignedEdge(Edge edge) {
  132 + ShortEdgeInfo edgeInfo = edge.toShortEdgeInfo();
  133 + if (this.assignedEdges != null && this.assignedEdges.contains(edgeInfo)) {
  134 + this.assignedEdges.remove(edgeInfo);
  135 + return true;
  136 + } else {
  137 + return false;
  138 + }
  139 + }
81 } 140 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.rule;
  17 +
  18 +public enum RuleChainType {
  19 + SYSTEM, EDGE
  20 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2020 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  20 + <modelVersion>4.0.0</modelVersion>
  21 + <parent>
  22 + <groupId>org.thingsboard</groupId>
  23 + <version>3.0.0-SNAPSHOT</version>
  24 + <artifactId>common</artifactId>
  25 + </parent>
  26 + <groupId>org.thingsboard.common</groupId>
  27 + <artifactId>edge-api</artifactId>
  28 + <packaging>jar</packaging>
  29 +
  30 + <name>Thingsboard Server Remote Edge wrapper</name>
  31 + <url>https://thingsboard.io</url>
  32 +
  33 + <properties>
  34 + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  35 + <main.dir>${basedir}/../..</main.dir>
  36 + </properties>
  37 +
  38 + <dependencies>
  39 + <dependency>
  40 + <groupId>org.thingsboard.common</groupId>
  41 + <artifactId>data</artifactId>
  42 + </dependency>
  43 + <dependency>
  44 + <groupId>org.thingsboard.common</groupId>
  45 + <artifactId>message</artifactId>
  46 + </dependency>
  47 + <dependency>
  48 + <groupId>com.google.code.gson</groupId>
  49 + <artifactId>gson</artifactId>
  50 + </dependency>
  51 + <dependency>
  52 + <groupId>org.slf4j</groupId>
  53 + <artifactId>slf4j-api</artifactId>
  54 + </dependency>
  55 + <dependency>
  56 + <groupId>org.slf4j</groupId>
  57 + <artifactId>log4j-over-slf4j</artifactId>
  58 + </dependency>
  59 + <dependency>
  60 + <groupId>ch.qos.logback</groupId>
  61 + <artifactId>logback-core</artifactId>
  62 + </dependency>
  63 + <dependency>
  64 + <groupId>ch.qos.logback</groupId>
  65 + <artifactId>logback-classic</artifactId>
  66 + </dependency>
  67 + <dependency>
  68 + <groupId>org.springframework</groupId>
  69 + <artifactId>spring-context</artifactId>
  70 + </dependency>
  71 + <dependency>
  72 + <groupId>org.springframework.boot</groupId>
  73 + <artifactId>spring-boot-starter-web</artifactId>
  74 + </dependency>
  75 + <dependency>
  76 + <groupId>io.netty</groupId>
  77 + <artifactId>netty-all</artifactId>
  78 + <scope>provided</scope>
  79 + </dependency>
  80 + <dependency>
  81 + <groupId>com.google.guava</groupId>
  82 + <artifactId>guava</artifactId>
  83 + </dependency>
  84 + <dependency>
  85 + <groupId>io.grpc</groupId>
  86 + <artifactId>grpc-netty</artifactId>
  87 + <exclusions>
  88 + <exclusion>
  89 + <artifactId>netty-transport</artifactId>
  90 + <groupId>io.netty</groupId>
  91 + </exclusion>
  92 + <exclusion>
  93 + <artifactId>netty-common</artifactId>
  94 + <groupId>io.netty</groupId>
  95 + </exclusion>
  96 + </exclusions>
  97 + </dependency>
  98 + <dependency>
  99 + <groupId>io.grpc</groupId>
  100 + <artifactId>grpc-protobuf</artifactId>
  101 + </dependency>
  102 + <dependency>
  103 + <groupId>io.grpc</groupId>
  104 + <artifactId>grpc-stub</artifactId>
  105 + </dependency>
  106 + <dependency>
  107 + <groupId>com.google.protobuf</groupId>
  108 + <artifactId>protobuf-java</artifactId>
  109 + </dependency>
  110 + </dependencies>
  111 +
  112 + <build>
  113 + <plugins>
  114 + <plugin>
  115 + <groupId>org.xolstice.maven.plugins</groupId>
  116 + <artifactId>protobuf-maven-plugin</artifactId>
  117 + </plugin>
  118 + </plugins>
  119 + </build>
  120 +
  121 + <distributionManagement>
  122 + <repository>
  123 + <id>thingsboard-repo-deploy</id>
  124 + <name>ThingsBoard Repo Deployment</name>
  125 + <url>https://repo.thingsboard.io/artifactory/libs-release-public</url>
  126 + </repository>
  127 + </distributionManagement>
  128 +
  129 +</project>
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.edge.exception;
  17 +
  18 +public class EdgeConnectionException extends RuntimeException {
  19 +
  20 + private static final long serialVersionUID = -4372754681230555723L;
  21 +
  22 + public EdgeConnectionException(String message) {
  23 + super(message);
  24 + }
  25 +
  26 + public EdgeConnectionException(String message, Throwable cause) {
  27 + super(message, cause);
  28 + }
  29 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.edge.rpc;
  17 +
  18 +import com.google.common.io.Resources;
  19 +import io.grpc.ManagedChannel;
  20 +import io.grpc.netty.GrpcSslContexts;
  21 +import io.grpc.netty.NettyChannelBuilder;
  22 +import io.grpc.stub.StreamObserver;
  23 +import lombok.extern.slf4j.Slf4j;
  24 +import org.springframework.beans.factory.annotation.Value;
  25 +import org.springframework.stereotype.Service;
  26 +import org.thingsboard.edge.exception.EdgeConnectionException;
  27 +import org.thingsboard.server.gen.edge.ConnectRequestMsg;
  28 +import org.thingsboard.server.gen.edge.ConnectResponseCode;
  29 +import org.thingsboard.server.gen.edge.ConnectResponseMsg;
  30 +import org.thingsboard.server.gen.edge.DownlinkMsg;
  31 +import org.thingsboard.server.gen.edge.EdgeConfiguration;
  32 +import org.thingsboard.server.gen.edge.EdgeRpcServiceGrpc;
  33 +import org.thingsboard.server.gen.edge.EntityUpdateMsg;
  34 +import org.thingsboard.server.gen.edge.RequestMsg;
  35 +import org.thingsboard.server.gen.edge.RequestMsgType;
  36 +import org.thingsboard.server.gen.edge.ResponseMsg;
  37 +import org.thingsboard.server.gen.edge.UplinkMsg;
  38 +import org.thingsboard.server.gen.edge.UplinkResponseMsg;
  39 +
  40 +import javax.net.ssl.SSLException;
  41 +import java.io.File;
  42 +import java.net.URISyntaxException;
  43 +import java.util.concurrent.TimeUnit;
  44 +import java.util.function.Consumer;
  45 +
  46 +@Service
  47 +@Slf4j
  48 +public class EdgeGrpcClient implements EdgeRpcClient {
  49 +
  50 + @Value("${cloud.rpc.host}")
  51 + private String rpcHost;
  52 + @Value("${cloud.rpc.port}")
  53 + private int rpcPort;
  54 + @Value("${cloud.rpc.timeout}")
  55 + private int timeoutSecs;
  56 + @Value("${cloud.rpc.ssl.enabled}")
  57 + private boolean sslEnabled;
  58 + @Value("${cloud.rpc.ssl.cert}")
  59 + private String certResource;
  60 +
  61 + private ManagedChannel channel;
  62 +
  63 + private StreamObserver<RequestMsg> inputStream;
  64 +
  65 + @Override
  66 + public void connect(String edgeKey,
  67 + String edgeSecret,
  68 + Consumer<UplinkResponseMsg> onUplinkResponse,
  69 + Consumer<EdgeConfiguration> onEdgeUpdate,
  70 + Consumer<EntityUpdateMsg> onEntityUpdate,
  71 + Consumer<DownlinkMsg> onDownlink,
  72 + Consumer<Exception> onError) {
  73 + NettyChannelBuilder builder = NettyChannelBuilder.forAddress(rpcHost, rpcPort).usePlaintext();
  74 + if (sslEnabled) {
  75 + try {
  76 + builder.sslContext(GrpcSslContexts.forClient().trustManager(new File(Resources.getResource(certResource).toURI())).build());
  77 + } catch (URISyntaxException | SSLException e) {
  78 + log.error("Failed to initialize channel!", e);
  79 + throw new RuntimeException(e);
  80 + }
  81 + }
  82 + channel = builder.build();
  83 + EdgeRpcServiceGrpc.EdgeRpcServiceStub stub = EdgeRpcServiceGrpc.newStub(channel);
  84 + log.info("[{}] Sending a connect request to the TB!", edgeKey);
  85 + this.inputStream = stub.handleMsgs(initOutputStream(edgeKey, onUplinkResponse, onEdgeUpdate, onEntityUpdate, onDownlink, onError));
  86 + this.inputStream.onNext(RequestMsg.newBuilder()
  87 + .setMsgType(RequestMsgType.CONNECT_RPC_MESSAGE)
  88 + .setConnectRequestMsg(ConnectRequestMsg.newBuilder().setEdgeRoutingKey(edgeKey).setEdgeSecret(edgeSecret).build())
  89 + .build());
  90 + }
  91 +
  92 + @Override
  93 + public void disconnect() throws InterruptedException {
  94 + inputStream.onCompleted();
  95 + if (channel != null) {
  96 + channel.shutdown().awaitTermination(timeoutSecs, TimeUnit.SECONDS);
  97 + }
  98 + }
  99 +
  100 + @Override
  101 + public void sendUplinkMsg(UplinkMsg msg) {
  102 + this.inputStream.onNext(RequestMsg.newBuilder()
  103 + .setMsgType(RequestMsgType.UPLINK_RPC_MESSAGE)
  104 + .setUplinkMsg(msg)
  105 + .build());
  106 + }
  107 +
  108 + private StreamObserver<ResponseMsg> initOutputStream(String edgeKey,
  109 + Consumer<UplinkResponseMsg> onUplinkResponse,
  110 + Consumer<EdgeConfiguration> onEdgeUpdate,
  111 + Consumer<EntityUpdateMsg> onEntityUpdate,
  112 + Consumer<DownlinkMsg> onDownlink,
  113 + Consumer<Exception> onError) {
  114 + return new StreamObserver<ResponseMsg>() {
  115 + @Override
  116 + public void onNext(ResponseMsg responseMsg) {
  117 + if (responseMsg.hasConnectResponseMsg()) {
  118 + ConnectResponseMsg connectResponseMsg = responseMsg.getConnectResponseMsg();
  119 + if (connectResponseMsg.getResponseCode().equals(ConnectResponseCode.ACCEPTED)) {
  120 + log.info("[{}] Configuration received: {}", edgeKey, connectResponseMsg.getConfiguration());
  121 + onEdgeUpdate.accept(connectResponseMsg.getConfiguration());
  122 + } else {
  123 + log.error("[{}] Failed to establish the connection! Code: {}. Error message: {}.", edgeKey, connectResponseMsg.getResponseCode(), connectResponseMsg.getErrorMsg());
  124 + onError.accept(new EdgeConnectionException("Failed to establish the connection! Response code: " + connectResponseMsg.getResponseCode().name()));
  125 + }
  126 + } else if (responseMsg.hasUplinkResponseMsg()) {
  127 + log.debug("[{}] Uplink response message received {}", edgeKey, responseMsg.getUplinkResponseMsg());
  128 + onUplinkResponse.accept(responseMsg.getUplinkResponseMsg());
  129 + } else if (responseMsg.hasEntityUpdateMsg()) {
  130 + log.debug("[{}] Entity update message received {}", edgeKey, responseMsg.getEntityUpdateMsg());
  131 + onEntityUpdate.accept(responseMsg.getEntityUpdateMsg());
  132 + } else if (responseMsg.hasDownlinkMsg()) {
  133 + log.debug("[{}] Downlink message received for rule chain {}", edgeKey, responseMsg.getDownlinkMsg());
  134 + onDownlink.accept(responseMsg.getDownlinkMsg());
  135 + }
  136 + }
  137 +
  138 + @Override
  139 + public void onError(Throwable t) {
  140 + log.debug("[{}] The rpc session received an error!", edgeKey, t);
  141 + onError.accept(new RuntimeException(t));
  142 + }
  143 +
  144 + @Override
  145 + public void onCompleted() {
  146 + log.debug("[{}] The rpc session was closed!", edgeKey);
  147 + }
  148 + };
  149 + }
  150 +}
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.edge.rpc;
  17 +
  18 +import org.thingsboard.server.gen.edge.DownlinkMsg;
  19 +import org.thingsboard.server.gen.edge.EdgeConfiguration;
  20 +import org.thingsboard.server.gen.edge.EntityUpdateMsg;
  21 +import org.thingsboard.server.gen.edge.UplinkMsg;
  22 +import org.thingsboard.server.gen.edge.UplinkResponseMsg;
  23 +
  24 +import java.util.function.Consumer;
  25 +
  26 +public interface EdgeRpcClient {
  27 +
  28 + void connect(String integrationKey,
  29 + String integrationSecret,
  30 + Consumer<UplinkResponseMsg> onUplinkResponse,
  31 + Consumer<EdgeConfiguration> onEdgeUpdate,
  32 + Consumer<EntityUpdateMsg> onEntityUpdate,
  33 + Consumer<DownlinkMsg> onDownlink,
  34 + Consumer<Exception> onError);
  35 +
  36 + void disconnect() throws InterruptedException;
  37 +
  38 + void sendUplinkMsg(UplinkMsg uplinkMsg) throws InterruptedException;
  39 +}