Commit e0f978992e67b1bf6738c3688c013070f41b67f1

Authored by Igor Kulikov
Committed by GitHub
2 parents dc875508 4e5c726e

Merge pull request #3811 from volodymyr-babak/develop/3.3-edge

Edge functionality
Showing 44 changed files with 3444 additions and 76 deletions

Too many changes to show.

To preserve performance only 44 of 371 files are displayed.

... ... @@ -33,5 +33,6 @@ pom.xml.versionsBackup
33 33 **/.env
34 34 .instance_id
35 35 rebuild-docker.sh
  36 +*/.run/**
36 37 .run/**
37 38 .run
\ No newline at end of file
... ...
... ... @@ -102,6 +102,10 @@
102 102 <artifactId>stats</artifactId>
103 103 </dependency>
104 104 <dependency>
  105 + <groupId>org.thingsboard.common</groupId>
  106 + <artifactId>edge-api</artifactId>
  107 + </dependency>
  108 + <dependency>
105 109 <groupId>org.thingsboard</groupId>
106 110 <artifactId>dao</artifactId>
107 111 <type>test-jar</type>
... ...
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Edge Root Rule Chain",
  5 + "type": "EDGE",
  6 + "firstRuleNodeId": null,
  7 + "root": true,
  8 + "debugMode": false,
  9 + "configuration": null
  10 + },
  11 + "metadata": {
  12 + "firstNodeIndex": 0,
  13 + "nodes": [
  14 + {
  15 + "additionalInfo": {
  16 + "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
  17 + "layoutX": 203,
  18 + "layoutY": 259
  19 + },
  20 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
  21 + "name": "Device Profile Node",
  22 + "debugMode": false,
  23 + "configuration": {
  24 + "persistAlarmRulesState": false,
  25 + "fetchAlarmRulesStateOnStart": false
  26 + }
  27 + },
  28 + {
  29 + "additionalInfo": {
  30 + "layoutX": 823,
  31 + "layoutY": 157
  32 + },
  33 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  34 + "name": "Save Timeseries",
  35 + "debugMode": false,
  36 + "configuration": {
  37 + "defaultTTL": 0
  38 + }
  39 + },
  40 + {
  41 + "additionalInfo": {
  42 + "layoutX": 824,
  43 + "layoutY": 52
  44 + },
  45 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  46 + "name": "Save Client Attributes",
  47 + "debugMode": false,
  48 + "configuration": {
  49 + "scope": "CLIENT_SCOPE"
  50 + }
  51 + },
  52 + {
  53 + "additionalInfo": {
  54 + "layoutX": 347,
  55 + "layoutY": 149
  56 + },
  57 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  58 + "name": "Message Type Switch",
  59 + "debugMode": false,
  60 + "configuration": {
  61 + "version": 0
  62 + }
  63 + },
  64 + {
  65 + "additionalInfo": {
  66 + "layoutX": 825,
  67 + "layoutY": 266
  68 + },
  69 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  70 + "name": "Log RPC from Device",
  71 + "debugMode": false,
  72 + "configuration": {
  73 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  74 + }
  75 + },
  76 + {
  77 + "additionalInfo": {
  78 + "layoutX": 824,
  79 + "layoutY": 378
  80 + },
  81 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  82 + "name": "Log Other",
  83 + "debugMode": false,
  84 + "configuration": {
  85 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  86 + }
  87 + },
  88 + {
  89 + "additionalInfo": {
  90 + "layoutX": 824,
  91 + "layoutY": 466
  92 + },
  93 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  94 + "name": "RPC Call Request",
  95 + "debugMode": false,
  96 + "configuration": {
  97 + "timeoutInSeconds": 60
  98 + }
  99 + },
  100 + {
  101 + "additionalInfo": {
  102 + "layoutX": 1134,
  103 + "layoutY": 132
  104 + },
  105 + "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
  106 + "name": "Push to cloud",
  107 + "debugMode": false,
  108 + "configuration": {
  109 + "version": 0
  110 + }
  111 + }
  112 + ],
  113 + "connections": [
  114 + {
  115 + "fromIndex": 0,
  116 + "toIndex": 3,
  117 + "type": "Success"
  118 + },
  119 + {
  120 + "fromIndex": 1,
  121 + "toIndex": 7,
  122 + "type": "Success"
  123 + },
  124 + {
  125 + "fromIndex": 2,
  126 + "toIndex": 7,
  127 + "type": "Success"
  128 + },
  129 + {
  130 + "fromIndex": 3,
  131 + "toIndex": 6,
  132 + "type": "RPC Request to Device"
  133 + },
  134 + {
  135 + "fromIndex": 3,
  136 + "toIndex": 5,
  137 + "type": "Other"
  138 + },
  139 + {
  140 + "fromIndex": 3,
  141 + "toIndex": 2,
  142 + "type": "Post attributes"
  143 + },
  144 + {
  145 + "fromIndex": 3,
  146 + "toIndex": 1,
  147 + "type": "Post telemetry"
  148 + },
  149 + {
  150 + "fromIndex": 3,
  151 + "toIndex": 4,
  152 + "type": "RPC Request from Device"
  153 + },
  154 + {
  155 + "fromIndex": 4,
  156 + "toIndex": 7,
  157 + "type": "Success"
  158 + }
  159 + ],
  160 + "ruleChainConnections": null
  161 + }
  162 +}
\ No newline at end of file
... ...
  1 +{
  2 + "widgetsBundle": {
  3 + "alias": "edge_widgets",
  4 + "title": "Edge widgets",
  5 + "image": null
  6 + },
  7 + "widgetTypes": [
  8 + {
  9 + "alias": "edges_overview",
  10 + "name": "Edge Quick Overview",
  11 + "descriptor": {
  12 + "type": "latest",
  13 + "sizeX": 7.5,
  14 + "sizeY": 5,
  15 + "resources": [],
  16 + "templateHtml": "<tb-edges-overview-widget \n [ctx]=\"ctx\">\n</tb-edges-overview-widget>",
  17 + "templateCss": "",
  18 + "controllerScript": "self.onInit = function() {\n};\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n dataKeysOptional: true\n };\n}\n\nself.onDestroy = function() {\n};\n",
  19 + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EdgeOverviewSettings\",\n \"properties\": {\n \"enableDefaultTitle\": {\n \"title\": \"Display default title\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableDefaultTitle\"\n ]\n}",
  20 + "dataKeySettingsSchema": "{}\n",
  21 + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"showTitleIcon\":true,\"titleIcon\":\"router\",\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{},\"title\":\"Edge Quick Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"widgetStyle\":{},\"actions\":{}}"
  22 + }
  23 + }
  24 + ]
  25 +}
... ...
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Edge Root Rule Chain",
  5 + "type": "EDGE",
  6 + "firstRuleNodeId": null,
  7 + "root": true,
  8 + "debugMode": false,
  9 + "configuration": null
  10 + },
  11 + "metadata": {
  12 + "firstNodeIndex": 0,
  13 + "nodes": [
  14 + {
  15 + "additionalInfo": {
  16 + "description": "Process incoming messages from devices with the alarm rules defined in the device profile. Dispatch all incoming messages with \"Success\" relation type.",
  17 + "layoutX": 203,
  18 + "layoutY": 259
  19 + },
  20 + "type": "org.thingsboard.rule.engine.profile.TbDeviceProfileNode",
  21 + "name": "Device Profile Node",
  22 + "debugMode": false,
  23 + "configuration": {
  24 + "persistAlarmRulesState": false,
  25 + "fetchAlarmRulesStateOnStart": false
  26 + }
  27 + },
  28 + {
  29 + "additionalInfo": {
  30 + "layoutX": 823,
  31 + "layoutY": 157
  32 + },
  33 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  34 + "name": "Save Timeseries",
  35 + "debugMode": false,
  36 + "configuration": {
  37 + "defaultTTL": 0
  38 + }
  39 + },
  40 + {
  41 + "additionalInfo": {
  42 + "layoutX": 824,
  43 + "layoutY": 52
  44 + },
  45 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  46 + "name": "Save Client Attributes",
  47 + "debugMode": false,
  48 + "configuration": {
  49 + "scope": "CLIENT_SCOPE"
  50 + }
  51 + },
  52 + {
  53 + "additionalInfo": {
  54 + "layoutX": 347,
  55 + "layoutY": 149
  56 + },
  57 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  58 + "name": "Message Type Switch",
  59 + "debugMode": false,
  60 + "configuration": {
  61 + "version": 0
  62 + }
  63 + },
  64 + {
  65 + "additionalInfo": {
  66 + "layoutX": 825,
  67 + "layoutY": 266
  68 + },
  69 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  70 + "name": "Log RPC from Device",
  71 + "debugMode": false,
  72 + "configuration": {
  73 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  74 + }
  75 + },
  76 + {
  77 + "additionalInfo": {
  78 + "layoutX": 824,
  79 + "layoutY": 378
  80 + },
  81 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  82 + "name": "Log Other",
  83 + "debugMode": false,
  84 + "configuration": {
  85 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  86 + }
  87 + },
  88 + {
  89 + "additionalInfo": {
  90 + "layoutX": 824,
  91 + "layoutY": 466
  92 + },
  93 + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCRequestNode",
  94 + "name": "RPC Call Request",
  95 + "debugMode": false,
  96 + "configuration": {
  97 + "timeoutInSeconds": 60
  98 + }
  99 + },
  100 + {
  101 + "additionalInfo": {
  102 + "layoutX": 1134,
  103 + "layoutY": 132
  104 + },
  105 + "type": "org.thingsboard.rule.engine.edge.TbMsgPushToCloudNode",
  106 + "name": "Push to cloud",
  107 + "debugMode": false,
  108 + "configuration": {
  109 + "version": 0
  110 + }
  111 + }
  112 + ],
  113 + "connections": [
  114 + {
  115 + "fromIndex": 0,
  116 + "toIndex": 3,
  117 + "type": "Success"
  118 + },
  119 + {
  120 + "fromIndex": 1,
  121 + "toIndex": 7,
  122 + "type": "Success"
  123 + },
  124 + {
  125 + "fromIndex": 2,
  126 + "toIndex": 7,
  127 + "type": "Success"
  128 + },
  129 + {
  130 + "fromIndex": 3,
  131 + "toIndex": 6,
  132 + "type": "RPC Request to Device"
  133 + },
  134 + {
  135 + "fromIndex": 3,
  136 + "toIndex": 5,
  137 + "type": "Other"
  138 + },
  139 + {
  140 + "fromIndex": 3,
  141 + "toIndex": 2,
  142 + "type": "Post attributes"
  143 + },
  144 + {
  145 + "fromIndex": 3,
  146 + "toIndex": 1,
  147 + "type": "Post telemetry"
  148 + },
  149 + {
  150 + "fromIndex": 3,
  151 + "toIndex": 4,
  152 + "type": "RPC Request from Device"
  153 + },
  154 + {
  155 + "fromIndex": 4,
  156 + "toIndex": 7,
  157 + "type": "Success"
  158 + }
  159 + ],
  160 + "ruleChainConnections": null
  161 + }
  162 +}
\ No newline at end of file
... ...
... ... @@ -2,6 +2,7 @@
2 2 "ruleChain": {
3 3 "additionalInfo": null,
4 4 "name": "Root Rule Chain",
  5 + "type": "CORE",
5 6 "firstRuleNodeId": null,
6 7 "root": true,
7 8 "debugMode": false,
... ...
  1 +--
  2 +-- Copyright © 2016-2021 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 uuid NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
  19 + created_time bigint NOT NULL,
  20 + additional_info varchar,
  21 + customer_id uuid,
  22 + root_rule_chain_id uuid,
  23 + type varchar(255),
  24 + name varchar(255),
  25 + label varchar(255),
  26 + routing_key varchar(255),
  27 + secret varchar(255),
  28 + edge_license_key varchar(30),
  29 + cloud_endpoint varchar(255),
  30 + search_text varchar(255),
  31 + tenant_id uuid,
  32 + CONSTRAINT edge_name_unq_key UNIQUE (tenant_id, name),
  33 + CONSTRAINT edge_routing_key_unq_key UNIQUE (routing_key)
  34 +);
  35 +
  36 +CREATE TABLE IF NOT EXISTS edge_event (
  37 + id uuid NOT NULL CONSTRAINT edge_event_pkey PRIMARY KEY,
  38 + created_time bigint NOT NULL,
  39 + edge_id uuid,
  40 + edge_event_type varchar(255),
  41 + edge_event_uid varchar(255),
  42 + entity_id uuid,
  43 + edge_event_action varchar(255),
  44 + body varchar(10000000),
  45 + tenant_id uuid,
  46 + ts bigint NOT NULL
  47 +);
... ...
  1 +--
  2 +-- Copyright © 2016-2021 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 OR REPLACE PROCEDURE cleanup_edge_events_by_ttl(IN ttl bigint, INOUT deleted bigint)
  18 + LANGUAGE plpgsql AS
  19 +$$
  20 +DECLARE
  21 + ttl_ts bigint;
  22 + ttl_deleted_count bigint DEFAULT 0;
  23 +BEGIN
  24 + IF ttl > 0 THEN
  25 + ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint;
  26 + EXECUTE format(
  27 + 'WITH deleted AS (DELETE FROM edge_event WHERE ts < %L::bigint RETURNING *) SELECT count(*) FROM deleted', ttl_ts) into ttl_deleted_count;
  28 + END IF;
  29 + RAISE NOTICE 'Edge events removed by ttl: %', ttl_deleted_count;
  30 + deleted := ttl_deleted_count;
  31 +END
  32 +$$;
... ...
... ... @@ -46,6 +46,7 @@ import org.thingsboard.server.common.msg.TbMsg;
46 46 import org.thingsboard.server.common.msg.queue.ServiceType;
47 47 import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
48 48 import org.thingsboard.server.common.msg.tools.TbRateLimits;
  49 +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
49 50 import org.thingsboard.server.dao.asset.AssetService;
50 51 import org.thingsboard.server.dao.attributes.AttributesService;
51 52 import org.thingsboard.server.dao.audit.AuditLogService;
... ... @@ -54,6 +55,8 @@ import org.thingsboard.server.dao.customer.CustomerService;
54 55 import org.thingsboard.server.dao.dashboard.DashboardService;
55 56 import org.thingsboard.server.dao.device.ClaimDevicesService;
56 57 import org.thingsboard.server.dao.device.DeviceService;
  58 +import org.thingsboard.server.dao.edge.EdgeEventService;
  59 +import org.thingsboard.server.dao.edge.EdgeService;
57 60 import org.thingsboard.server.dao.entityview.EntityViewService;
58 61 import org.thingsboard.server.dao.event.EventService;
59 62 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
... ... @@ -69,7 +72,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
69 72 import org.thingsboard.server.queue.usagestats.TbApiUsageClient;
70 73 import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
71 74 import org.thingsboard.server.service.component.ComponentDiscoveryService;
72   -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  75 +import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
73 76 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
74 77 import org.thingsboard.server.service.executors.ExternalCallExecutorService;
75 78 import org.thingsboard.server.service.executors.SharedEventLoopGroupService;
... ... @@ -296,6 +299,18 @@ public class ActorSystemContext {
296 299 @Getter
297 300 private TbCoreDeviceRpcService tbCoreDeviceRpcService;
298 301
  302 + @Lazy
  303 + @Autowired(required = false)
  304 + @Getter private EdgeService edgeService;
  305 +
  306 + @Lazy
  307 + @Autowired(required = false)
  308 + @Getter private EdgeEventService edgeEventService;
  309 +
  310 + @Lazy
  311 + @Autowired(required = false)
  312 + @Getter private EdgeRpcService edgeRpcService;
  313 +
299 314 @Value("${actors.session.max_concurrent_sessions_per_device:1}")
300 315 @Getter
301 316 private long maxConcurrentSessionsPerDevice;
... ... @@ -320,6 +335,9 @@ public class ActorSystemContext {
320 335 @Getter
321 336 private long statisticsPersistFrequency;
322 337
  338 + @Value("${edges.enabled}")
  339 + @Getter
  340 + private boolean edgesEnabled;
323 341
324 342 @Scheduled(fixedDelayString = "${actors.statistics.js_print_interval_ms}")
325 343 public void printStats() {
... ...
... ... @@ -35,10 +35,12 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
35 35 import org.thingsboard.server.common.msg.MsgType;
36 36 import org.thingsboard.server.common.msg.TbActorMsg;
37 37 import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
  38 +import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
38 39 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
39 40 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
40 41 import org.thingsboard.server.common.msg.queue.RuleEngineException;
41 42 import org.thingsboard.server.common.msg.queue.ServiceType;
  43 +import org.thingsboard.server.dao.model.ModelConstants;
42 44 import org.thingsboard.server.dao.tenant.TenantService;
43 45 import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
44 46 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
... ... @@ -89,10 +91,15 @@ public class AppActor extends ContextAwareActor {
89 91 case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
90 92 case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
91 93 case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
  94 + case DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG:
92 95 case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
  96 + case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
93 97 case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
94 98 onToDeviceActorMsg((TenantAwareMsg) msg, true);
95 99 break;
  100 + case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:
  101 + onToTenantActorMsg((EdgeEventUpdateMsg) msg);
  102 + break;
96 103 default:
97 104 return false;
98 105 }
... ... @@ -192,6 +199,20 @@ public class AppActor extends ContextAwareActor {
192 199 () -> new TenantActor.ActorCreator(systemContext, tenantId));
193 200 }
194 201
  202 + private void onToTenantActorMsg(EdgeEventUpdateMsg msg) {
  203 + TbActorRef target = null;
  204 + if (ModelConstants.SYSTEM_TENANT.equals(msg.getTenantId())) {
  205 + log.warn("Message has system tenant id: {}", msg);
  206 + } else {
  207 + target = getOrCreateTenantActor(msg.getTenantId());
  208 + }
  209 + if (target != null) {
  210 + target.tellWithHighPriority(msg);
  211 + } else {
  212 + log.debug("[{}] Invalid edge event update msg: {}", msg.getTenantId(), msg);
  213 + }
  214 + }
  215 +
195 216 public static class ActorCreator extends ContextBasedCreator {
196 217
197 218 public ActorCreator(ActorSystemContext context) {
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.actors.device;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
  20 +import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
20 21 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
21 22 import org.thingsboard.server.actors.ActorSystemContext;
22 23 import org.thingsboard.server.actors.TbActorCtx;
... ... @@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
26 27 import org.thingsboard.server.common.data.id.TenantId;
27 28 import org.thingsboard.server.common.msg.TbActorMsg;
28 29 import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
  30 +import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
29 31 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
30 32 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
31 33
... ... @@ -70,12 +72,18 @@ public class DeviceActor extends ContextAwareActor {
70 72 case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
71 73 processor.processRpcRequest(ctx, (ToDeviceRpcRequestActorMsg) msg);
72 74 break;
  75 + case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
  76 + processor.processRpcResponsesFromEdge(ctx, (FromDeviceRpcResponseActorMsg) msg);
  77 + break;
73 78 case DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG:
74 79 processor.processServerSideRpcTimeout(ctx, (DeviceActorServerSideRpcTimeoutMsg) msg);
75 80 break;
76 81 case SESSION_TIMEOUT_MSG:
77 82 processor.checkSessionsTimeout();
78 83 break;
  84 + case DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG:
  85 + processor.processEdgeUpdate((DeviceEdgeUpdateMsg) msg);
  86 + break;
79 87 default:
80 88 return false;
81 89 }
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.actors.device;
17 17
  18 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 19 import com.google.common.util.concurrent.FutureCallback;
19 20 import com.google.common.util.concurrent.Futures;
20 21 import com.google.common.util.concurrent.ListenableFuture;
... ... @@ -24,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
24 25 import org.apache.commons.collections.CollectionUtils;
25 26 import org.thingsboard.rule.engine.api.RpcError;
26 27 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
  28 +import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
27 29 import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
28 30 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
29 31 import org.thingsboard.server.actors.ActorSystemContext;
... ... @@ -31,11 +33,17 @@ import org.thingsboard.server.actors.TbActorCtx;
31 33 import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
32 34 import org.thingsboard.server.common.data.DataConstants;
33 35 import org.thingsboard.server.common.data.Device;
  36 +import org.thingsboard.server.common.data.edge.EdgeEvent;
  37 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
  38 +import org.thingsboard.server.common.data.edge.EdgeEventType;
34 39 import org.thingsboard.server.common.data.id.DeviceId;
  40 +import org.thingsboard.server.common.data.id.EdgeId;
35 41 import org.thingsboard.server.common.data.id.TenantId;
36 42 import org.thingsboard.server.common.data.kv.AttributeKey;
37 43 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
38 44 import org.thingsboard.server.common.data.kv.KvEntry;
  45 +import org.thingsboard.server.common.data.relation.EntityRelation;
  46 +import org.thingsboard.server.common.data.relation.RelationTypeGroup;
39 47 import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
40 48 import org.thingsboard.server.common.data.security.DeviceCredentials;
41 49 import org.thingsboard.server.common.data.security.DeviceCredentialsType;
... ... @@ -68,6 +76,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceAct
68 76 import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto;
69 77 import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
70 78 import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
  79 +import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
71 80 import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
72 81 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
73 82
... ... @@ -103,6 +112,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
103 112 private String deviceName;
104 113 private String deviceType;
105 114 private TbMsgMetaData defaultMetaData;
  115 + private EdgeId edgeId;
106 116
107 117 DeviceActorMessageProcessor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
108 118 super(systemContext);
... ... @@ -125,12 +135,32 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
125 135 this.defaultMetaData = new TbMsgMetaData();
126 136 this.defaultMetaData.putValue("deviceName", deviceName);
127 137 this.defaultMetaData.putValue("deviceType", deviceType);
  138 + if (systemContext.isEdgesEnabled()) {
  139 + this.edgeId = findRelatedEdgeId();
  140 + }
128 141 return true;
129 142 } else {
130 143 return false;
131 144 }
132 145 }
133 146
  147 + private EdgeId findRelatedEdgeId() {
  148 + List<EntityRelation> result =
  149 + systemContext.getRelationService().findByToAndType(tenantId, deviceId, EntityRelation.EDGE_TYPE, RelationTypeGroup.COMMON);
  150 + if (result != null && result.size() > 0) {
  151 + EntityRelation relationToEdge = result.get(0);
  152 + if (relationToEdge.getFrom() != null && relationToEdge.getFrom().getId() != null) {
  153 + log.trace("[{}][{}] found edge [{}] for device", tenantId, deviceId, relationToEdge.getFrom().getId());
  154 + return new EdgeId(relationToEdge.getFrom().getId());
  155 + } else {
  156 + log.trace("[{}][{}] edge relation is empty {}", tenantId, deviceId, relationToEdge);
  157 + }
  158 + } else {
  159 + log.trace("[{}][{}] device doesn't have any related edge", tenantId, deviceId);
  160 + }
  161 + return null;
  162 + }
  163 +
134 164 void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) {
135 165 ToDeviceRpcRequest request = msg.getMsg();
136 166 ToDeviceRpcRequestBody body = request.getBody();
... ... @@ -143,15 +173,22 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
143 173 return;
144 174 }
145 175
146   - boolean sent = rpcSubscriptions.size() > 0;
147   - Set<UUID> syncSessionSet = new HashSet<>();
148   - rpcSubscriptions.forEach((key, value) -> {
149   - sendToTransport(rpcRequest, key, value.getNodeId());
150   - if (SessionType.SYNC == value.getType()) {
151   - syncSessionSet.add(key);
152   - }
153   - });
154   - syncSessionSet.forEach(rpcSubscriptions::remove);
  176 + boolean sent;
  177 + if (systemContext.isEdgesEnabled() && edgeId != null) {
  178 + log.debug("[{}][{}] device is related to edge [{}]. Saving RPC request to edge queue", tenantId, deviceId, edgeId.getId());
  179 + saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId());
  180 + sent = true;
  181 + } else {
  182 + sent = rpcSubscriptions.size() > 0;
  183 + Set<UUID> syncSessionSet = new HashSet<>();
  184 + rpcSubscriptions.forEach((key, value) -> {
  185 + sendToTransport(rpcRequest, key, value.getNodeId());
  186 + if (SessionType.SYNC == value.getType()) {
  187 + syncSessionSet.add(key);
  188 + }
  189 + });
  190 + syncSessionSet.forEach(rpcSubscriptions::remove);
  191 + }
155 192
156 193 if (request.isOneway() && sent) {
157 194 log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
... ... @@ -166,6 +203,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
166 203 }
167 204 }
168 205
  206 + void processRpcResponsesFromEdge(TbActorCtx context, FromDeviceRpcResponseActorMsg responseMsg) {
  207 + log.debug("[{}] Processing rpc command response from edge session", deviceId);
  208 + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
  209 + boolean success = requestMd != null;
  210 + if (success) {
  211 + systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(responseMsg.getMsg());
  212 + } else {
  213 + log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
  214 + }
  215 + }
  216 +
169 217 private void registerPendingRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
170 218 toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
171 219 DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout);
... ... @@ -498,6 +546,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
498 546 this.defaultMetaData.putValue("deviceType", deviceType);
499 547 }
500 548
  549 + void processEdgeUpdate(DeviceEdgeUpdateMsg msg) {
  550 + log.trace("[{}] Processing edge update {}", deviceId, msg);
  551 + this.edgeId = msg.getEdgeId();
  552 + }
  553 +
501 554 private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) {
502 555 ToTransportMsg msg = ToTransportMsg.newBuilder()
503 556 .setSessionIdMSB(sessionInfo.getSessionIdMSB())
... ... @@ -530,6 +583,36 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
530 583 systemContext.getTbCoreToTransportService().process(nodeId, msg);
531 584 }
532 585
  586 + private void saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) {
  587 + EdgeEvent edgeEvent = new EdgeEvent();
  588 + edgeEvent.setTenantId(tenantId);
  589 + edgeEvent.setAction(EdgeEventActionType.RPC_CALL);
  590 + edgeEvent.setEntityId(deviceId.getId());
  591 + edgeEvent.setType(EdgeEventType.DEVICE);
  592 +
  593 + ObjectNode body = mapper.createObjectNode();
  594 + body.put("requestId", requestId);
  595 + body.put("requestUUID", msg.getId().toString());
  596 + body.put("oneway", msg.isOneway());
  597 + body.put("expirationTime", msg.getExpirationTime());
  598 + body.put("method", msg.getBody().getMethod());
  599 + body.put("params", msg.getBody().getParams());
  600 + edgeEvent.setBody(body);
  601 +
  602 + edgeEvent.setEdgeId(edgeId);
  603 + ListenableFuture<EdgeEvent> future = systemContext.getEdgeEventService().saveAsync(edgeEvent);
  604 + Futures.addCallback(future, new FutureCallback<EdgeEvent>() {
  605 + @Override
  606 + public void onSuccess( EdgeEvent result) {
  607 + systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId);
  608 + }
  609 +
  610 + @Override
  611 + public void onFailure(Throwable t) {
  612 + log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t);
  613 + }
  614 + }, systemContext.getDbCallbackExecutor());
  615 + }
533 616
534 617 private List<TsKvProto> toTsKvProtos(@Nullable List<AttributeKvEntry> result) {
535 618 List<TsKvProto> clientAttributes;
... ...
... ... @@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.TenantProfile;
42 42 import org.thingsboard.server.common.data.alarm.Alarm;
43 43 import org.thingsboard.server.common.data.asset.Asset;
44 44 import org.thingsboard.server.common.data.id.DeviceId;
  45 +import org.thingsboard.server.common.data.id.EdgeId;
45 46 import org.thingsboard.server.common.data.id.EntityId;
46 47 import org.thingsboard.server.common.data.id.RuleChainId;
47 48 import org.thingsboard.server.common.data.id.RuleNodeId;
... ... @@ -62,6 +63,8 @@ import org.thingsboard.server.dao.cassandra.CassandraCluster;
62 63 import org.thingsboard.server.dao.customer.CustomerService;
63 64 import org.thingsboard.server.dao.dashboard.DashboardService;
64 65 import org.thingsboard.server.dao.device.DeviceService;
  66 +import org.thingsboard.server.dao.edge.EdgeEventService;
  67 +import org.thingsboard.server.dao.edge.EdgeService;
65 68 import org.thingsboard.server.dao.entityview.EntityViewService;
66 69 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
67 70 import org.thingsboard.server.dao.nosql.TbResultSetFuture;
... ... @@ -319,6 +322,11 @@ class DefaultTbContext implements TbContext {
319 322 return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action, queueName, ruleChainId);
320 323 }
321 324
  325 + @Override
  326 + public void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId) {
  327 + mainCtx.getClusterService().onEdgeEventUpdate(tenantId, edgeId);
  328 + }
  329 +
322 330 public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) {
323 331 return entityActionMsg(entity, id, ruleNodeId, action, ServiceQueue.MAIN, null);
324 332 }
... ... @@ -478,6 +486,16 @@ class DefaultTbContext implements TbContext {
478 486 }
479 487
480 488 @Override
  489 + public EdgeService getEdgeService() {
  490 + return mainCtx.getEdgeService();
  491 + }
  492 +
  493 + @Override
  494 + public EdgeEventService getEdgeEventService() {
  495 + return mainCtx.getEdgeEventService();
  496 + }
  497 +
  498 + @Override
481 499 public EventLoopGroup getSharedEventLoop() {
482 500 return mainCtx.getSharedEventLoopGroupService().getSharedEventLoopGroup();
483 501 }
... ...
... ... @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
32 32 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
33 33 import org.thingsboard.server.common.data.relation.EntityRelation;
34 34 import org.thingsboard.server.common.data.rule.RuleChain;
  35 +import org.thingsboard.server.common.data.rule.RuleChainType;
35 36 import org.thingsboard.server.common.data.rule.RuleNode;
36 37 import org.thingsboard.server.common.msg.TbMsg;
37 38 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
... ... @@ -99,7 +100,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
99 100 public void start(TbActorCtx context) {
100 101 if (!started) {
101 102 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
102   - if (ruleChain != null) {
  103 + if (ruleChain != null && RuleChainType.CORE.equals(ruleChain.getType())) {
103 104 List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
104 105 log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
105 106 // Creating and starting the actors;
... ... @@ -119,7 +120,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
119 120 @Override
120 121 public void onUpdate(TbActorCtx context) {
121 122 RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
122   - if (ruleChain != null) {
  123 + if (ruleChain != null && RuleChainType.CORE.equals(ruleChain.getType())) {
123 124 ruleChainName = ruleChain.getName();
124 125 List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
125 126 log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
... ...
... ... @@ -23,13 +23,13 @@ import org.thingsboard.server.actors.TbEntityActorId;
23 23 import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
24 24 import org.thingsboard.server.actors.service.ContextAwareActor;
25 25 import org.thingsboard.server.actors.service.DefaultActorService;
26   -import org.thingsboard.server.actors.tenant.TenantActor;
27 26 import org.thingsboard.server.common.data.EntityType;
28 27 import org.thingsboard.server.common.data.id.EntityId;
29 28 import org.thingsboard.server.common.data.id.RuleChainId;
30 29 import org.thingsboard.server.common.data.id.TenantId;
31 30 import org.thingsboard.server.common.data.page.PageDataIterable;
32 31 import org.thingsboard.server.common.data.rule.RuleChain;
  32 +import org.thingsboard.server.common.data.rule.RuleChainType;
33 33 import org.thingsboard.server.common.msg.TbActorMsg;
34 34 import org.thingsboard.server.dao.rule.RuleChainService;
35 35
... ... @@ -55,7 +55,7 @@ public abstract class RuleChainManagerActor extends ContextAwareActor {
55 55 }
56 56
57 57 protected void initRuleChains() {
58   - for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChains(tenantId, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
  58 + for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
59 59 RuleChainId ruleChainId = ruleChain.getId();
60 60 log.debug("[{}|{}] Creating rule chain actor", ruleChainId.getEntityType(), ruleChain.getId());
61 61 TbActorRef actorRef = getOrCreateActor(ruleChainId, id -> ruleChain);
... ... @@ -65,13 +65,13 @@ public abstract class RuleChainManagerActor extends ContextAwareActor {
65 65 }
66 66
67 67 protected void destroyRuleChains() {
68   - for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChains(tenantId, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
  68 + for (RuleChain ruleChain : new PageDataIterable<>(link -> ruleChainService.findTenantRuleChainsByType(tenantId, RuleChainType.CORE, link), ContextAwareActor.ENTITY_PACK_LIMIT)) {
69 69 ctx.stop(new TbEntityActorId(ruleChain.getId()));
70 70 }
71 71 }
72 72
73 73 protected void visit(RuleChain entity, TbActorRef actorRef) {
74   - if (entity != null && entity.isRoot()) {
  74 + if (entity != null && entity.isRoot() && entity.getType().equals(RuleChainType.CORE)) {
75 75 rootChain = entity;
76 76 rootChainActor = actorRef;
77 77 }
... ...
... ... @@ -33,21 +33,27 @@ import org.thingsboard.server.common.data.ApiUsageState;
33 33 import org.thingsboard.server.common.data.EntityType;
34 34 import org.thingsboard.server.common.data.Tenant;
35 35 import org.thingsboard.server.common.data.TenantProfile;
  36 +import org.thingsboard.server.common.data.edge.Edge;
36 37 import org.thingsboard.server.common.data.id.DeviceId;
  38 +import org.thingsboard.server.common.data.id.EdgeId;
37 39 import org.thingsboard.server.common.data.id.EntityId;
38 40 import org.thingsboard.server.common.data.id.RuleChainId;
39 41 import org.thingsboard.server.common.data.id.TenantId;
  42 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
40 43 import org.thingsboard.server.common.data.rule.RuleChain;
  44 +import org.thingsboard.server.common.data.rule.RuleChainType;
41 45 import org.thingsboard.server.common.msg.MsgType;
42 46 import org.thingsboard.server.common.msg.TbActorMsg;
43 47 import org.thingsboard.server.common.msg.TbMsg;
44 48 import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
45 49 import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
  50 +import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
46 51 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
47 52 import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
48 53 import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
49 54 import org.thingsboard.server.common.msg.queue.RuleEngineException;
50 55 import org.thingsboard.server.common.msg.queue.ServiceType;
  56 +import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
51 57 import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
52 58
53 59 import java.util.List;
... ... @@ -155,13 +161,18 @@ public class TenantActor extends RuleChainManagerActor {
155 161 case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
156 162 case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
157 163 case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
  164 + case DEVICE_EDGE_UPDATE_TO_DEVICE_ACTOR_MSG:
158 165 case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
  166 + case DEVICE_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
159 167 case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
160 168 onToDeviceActorMsg((DeviceAwareMsg) msg, true);
161 169 break;
162 170 case RULE_CHAIN_TO_RULE_CHAIN_MSG:
163 171 onRuleChainMsg((RuleChainAwareMsg) msg);
164 172 break;
  173 + case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:
  174 + onToEdgeSessionMsg((EdgeEventUpdateMsg) msg);
  175 + break;
165 176 default:
166 177 return false;
167 178 }
... ... @@ -230,14 +241,26 @@ public class TenantActor extends RuleChainManagerActor {
230 241 log.info("[{}] Received API state update. Going to ENABLE Rule Engine execution.", tenantId);
231 242 initRuleChains();
232 243 }
233   - }
234   - if (isRuleEngineForCurrentTenant) {
  244 + } else if (msg.getEntityId().getEntityType() == EntityType.EDGE) {
  245 + EdgeId edgeId = new EdgeId(msg.getEntityId().getId());
  246 + EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService();
  247 + if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
  248 + edgeRpcService.deleteEdge(edgeId);
  249 + } else {
  250 + Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId);
  251 + if (msg.getEvent() == ComponentLifecycleEvent.UPDATED) {
  252 + edgeRpcService.updateEdge(edge);
  253 + }
  254 + }
  255 + } else if (isRuleEngineForCurrentTenant) {
235 256 TbActorRef target = getEntityActorRef(msg.getEntityId());
236 257 if (target != null) {
237 258 if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
238 259 RuleChain ruleChain = systemContext.getRuleChainService().
239 260 findRuleChainById(tenantId, new RuleChainId(msg.getEntityId().getId()));
240   - visit(ruleChain, target);
  261 + if (ruleChain != null && RuleChainType.CORE.equals(ruleChain.getType())) {
  262 + visit(ruleChain, target);
  263 + }
241 264 }
242 265 target.tellWithHighPriority(msg);
243 266 } else {
... ... @@ -252,6 +275,11 @@ public class TenantActor extends RuleChainManagerActor {
252 275 () -> new DeviceActorCreator(systemContext, tenantId, deviceId));
253 276 }
254 277
  278 + private void onToEdgeSessionMsg(EdgeEventUpdateMsg msg) {
  279 + log.trace("[{}] onToEdgeSessionMsg [{}]", msg.getTenantId(), msg);
  280 + systemContext.getEdgeRpcService().onEdgeEvent(msg.getEdgeId());
  281 + }
  282 +
255 283 public static class ActorCreator extends ContextBasedCreator {
256 284
257 285 private final TenantId tenantId;
... ...
... ... @@ -71,7 +71,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
71 71 public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
72 72 public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
73 73 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
74   - protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**"};
  74 + protected static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/assets/**", "/static/**", "/api/noauth/**", "/webjars/**", "/api/license/**"};
75 75 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
76 76 public static final String WS_TOKEN_BASED_AUTH_ENTRY_POINT = "/api/ws/**";
77 77
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
34 34 import org.thingsboard.server.common.data.alarm.AlarmSeverity;
35 35 import org.thingsboard.server.common.data.alarm.AlarmStatus;
36 36 import org.thingsboard.server.common.data.audit.ActionType;
  37 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
37 38 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
38 39 import org.thingsboard.server.common.data.exception.ThingsboardException;
39 40 import org.thingsboard.server.common.data.id.AlarmId;
... ... @@ -93,6 +94,9 @@ public class AlarmController extends BaseController {
93 94 logEntityAction(savedAlarm.getOriginator(), savedAlarm,
94 95 getCurrentUser().getCustomerId(),
95 96 alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
  97 +
  98 + sendEntityNotificationMsg(getTenantId(), savedAlarm.getId(), alarm.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
  99 +
96 100 return savedAlarm;
97 101 } catch (Exception e) {
98 102 logEntityAction(emptyId(EntityType.ALARM), alarm,
... ... @@ -109,8 +113,11 @@ public class AlarmController extends BaseController {
109 113 try {
110 114 AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
111 115 checkAlarmId(alarmId, Operation.WRITE);
  116 +
  117 + sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.DELETED);
  118 +
112 119 return alarmService.deleteAlarm(getTenantId(), alarmId);
113   - } catch (Exception e) {
  120 + } catch (Exception e) {
114 121 throw handleException(e);
115 122 }
116 123 }
... ... @@ -128,6 +135,8 @@ public class AlarmController extends BaseController {
128 135 alarm.setAckTs(ackTs);
129 136 alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK);
130 137 logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
  138 +
  139 + sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.ALARM_ACK);
131 140 } catch (Exception e) {
132 141 throw handleException(e);
133 142 }
... ... @@ -146,6 +155,8 @@ public class AlarmController extends BaseController {
146 155 alarm.setClearTs(clearTs);
147 156 alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
148 157 logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
  158 +
  159 + sendEntityNotificationMsg(getTenantId(), alarmId, EdgeEventActionType.ALARM_CLEAR);
149 160 } catch (Exception e) {
150 161 throw handleException(e);
151 162 }
... ...
... ... @@ -33,14 +33,18 @@ import org.thingsboard.server.common.data.asset.Asset;
33 33 import org.thingsboard.server.common.data.asset.AssetInfo;
34 34 import org.thingsboard.server.common.data.asset.AssetSearchQuery;
35 35 import org.thingsboard.server.common.data.audit.ActionType;
  36 +import org.thingsboard.server.common.data.edge.Edge;
  37 +import org.thingsboard.server.common.data.edge.EdgeEventType;
36 38 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
  39 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
37 40 import org.thingsboard.server.common.data.exception.ThingsboardException;
38 41 import org.thingsboard.server.common.data.id.AssetId;
39 42 import org.thingsboard.server.common.data.id.CustomerId;
  43 +import org.thingsboard.server.common.data.id.EdgeId;
40 44 import org.thingsboard.server.common.data.id.TenantId;
41 45 import org.thingsboard.server.common.data.page.PageData;
42 46 import org.thingsboard.server.common.data.page.PageLink;
43   -import org.thingsboard.server.common.data.security.Authority;
  47 +import org.thingsboard.server.common.data.page.TimePageLink;
44 48 import org.thingsboard.server.dao.exception.IncorrectParameterException;
45 49 import org.thingsboard.server.dao.model.ModelConstants;
46 50 import org.thingsboard.server.queue.util.TbCoreComponent;
... ... @@ -54,6 +58,8 @@ import java.util.stream.Collectors;
54 58
55 59 import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
56 60
  61 +import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
  62 +
57 63 @RestController
58 64 @TbCoreComponent
59 65 @RequestMapping("/api")
... ... @@ -98,7 +104,7 @@ public class AssetController extends BaseController {
98 104
99 105 asset.setTenantId(getCurrentUser().getTenantId());
100 106
101   - checkEntity(asset.getId(), asset, Resource.ASSET);
  107 + checkEntity(asset.getId(), asset, Resource.ASSET);
102 108
103 109 Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
104 110
... ... @@ -106,6 +112,10 @@ public class AssetController extends BaseController {
106 112 savedAsset.getCustomerId(),
107 113 asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
108 114
  115 + if (asset.getId() != null) {
  116 + sendEntityNotificationMsg(savedAsset.getTenantId(), savedAsset.getId(), EdgeEventActionType.UPDATED);
  117 + }
  118 +
109 119 return savedAsset;
110 120 } catch (Exception e) {
111 121 logEntityAction(emptyId(EntityType.ASSET), asset,
... ... @@ -122,12 +132,16 @@ public class AssetController extends BaseController {
122 132 try {
123 133 AssetId assetId = new AssetId(toUUID(strAssetId));
124 134 Asset asset = checkAssetId(assetId, Operation.DELETE);
  135 +
  136 + List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), assetId);
  137 +
125 138 assetService.deleteAsset(getTenantId(), assetId);
126 139
127 140 logEntityAction(assetId, asset,
128 141 asset.getCustomerId(),
129 142 ActionType.DELETED, null, strAssetId);
130 143
  144 + sendDeleteNotificationMsg(getTenantId(), assetId, relatedEdgeIds);
131 145 } catch (Exception e) {
132 146 logEntityAction(emptyId(EntityType.ASSET),
133 147 null,
... ... @@ -157,6 +171,9 @@ public class AssetController extends BaseController {
157 171 savedAsset.getCustomerId(),
158 172 ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName());
159 173
  174 + sendEntityAssignToCustomerNotificationMsg(savedAsset.getTenantId(), savedAsset.getId(),
  175 + customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
  176 +
160 177 return savedAsset;
161 178 } catch (Exception e) {
162 179
... ... @@ -188,6 +205,9 @@ public class AssetController extends BaseController {
188 205 asset.getCustomerId(),
189 206 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strAssetId, customer.getId().toString(), customer.getName());
190 207
  208 + sendEntityAssignToCustomerNotificationMsg(savedAsset.getTenantId(), savedAsset.getId(),
  209 + customer.getId(), EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
  210 +
191 211 return savedAsset;
192 212 } catch (Exception e) {
193 213
... ... @@ -401,4 +421,113 @@ public class AssetController extends BaseController {
401 421 throw handleException(e);
402 422 }
403 423 }
  424 +
  425 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  426 + @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
  427 + @ResponseBody
  428 + public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId,
  429 + @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
  430 + checkParameter(EDGE_ID, strEdgeId);
  431 + checkParameter(ASSET_ID, strAssetId);
  432 + try {
  433 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  434 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  435 +
  436 + AssetId assetId = new AssetId(toUUID(strAssetId));
  437 + checkAssetId(assetId, Operation.ASSIGN_TO_EDGE);
  438 +
  439 + Asset savedAsset = checkNotNull(assetService.assignAssetToEdge(getTenantId(), assetId, edgeId));
  440 +
  441 + logEntityAction(assetId, savedAsset,
  442 + savedAsset.getCustomerId(),
  443 + ActionType.ASSIGNED_TO_EDGE, null, strAssetId, strEdgeId, edge.getName());
  444 +
  445 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedAsset.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE);
  446 +
  447 + return savedAsset;
  448 + } catch (Exception e) {
  449 +
  450 + logEntityAction(emptyId(EntityType.ASSET), null,
  451 + null,
  452 + ActionType.ASSIGNED_TO_EDGE, e, strAssetId, strEdgeId);
  453 +
  454 + throw handleException(e);
  455 + }
  456 + }
  457 +
  458 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  459 + @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE)
  460 + @ResponseBody
  461 + public Asset unassignAssetFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
  462 + @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
  463 + checkParameter(EDGE_ID, strEdgeId);
  464 + checkParameter(ASSET_ID, strAssetId);
  465 + try {
  466 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  467 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  468 +
  469 + AssetId assetId = new AssetId(toUUID(strAssetId));
  470 + Asset asset = checkAssetId(assetId, Operation.UNASSIGN_FROM_EDGE);
  471 +
  472 + Asset savedAsset = checkNotNull(assetService.unassignAssetFromEdge(getTenantId(), assetId, edgeId));
  473 +
  474 + logEntityAction(assetId, asset,
  475 + asset.getCustomerId(),
  476 + ActionType.UNASSIGNED_FROM_EDGE, null, strAssetId, strEdgeId, edge.getName());
  477 +
  478 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedAsset.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE);
  479 +
  480 + return savedAsset;
  481 + } catch (Exception e) {
  482 +
  483 + logEntityAction(emptyId(EntityType.ASSET), null,
  484 + null,
  485 + ActionType.UNASSIGNED_FROM_EDGE, e, strAssetId, strEdgeId);
  486 +
  487 + throw handleException(e);
  488 + }
  489 + }
  490 +
  491 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  492 + @RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
  493 + @ResponseBody
  494 + public PageData<Asset> getEdgeAssets(
  495 + @PathVariable(EDGE_ID) String strEdgeId,
  496 + @RequestParam int pageSize,
  497 + @RequestParam int page,
  498 + @RequestParam(required = false) String type,
  499 + @RequestParam(required = false) String textSearch,
  500 + @RequestParam(required = false) String sortProperty,
  501 + @RequestParam(required = false) String sortOrder,
  502 + @RequestParam(required = false) Long startTime,
  503 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
  504 + checkParameter(EDGE_ID, strEdgeId);
  505 + try {
  506 + TenantId tenantId = getCurrentUser().getTenantId();
  507 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  508 + checkEdgeId(edgeId, Operation.READ);
  509 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  510 + PageData<Asset> nonFilteredResult;
  511 + if (type != null && type.trim().length() > 0) {
  512 + nonFilteredResult = assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink);
  513 + } else {
  514 + nonFilteredResult = assetService.findAssetsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
  515 + }
  516 + List<Asset> filteredAssets = nonFilteredResult.getData().stream().filter(asset -> {
  517 + try {
  518 + accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, Operation.READ, asset.getId(), asset);
  519 + return true;
  520 + } catch (ThingsboardException e) {
  521 + return false;
  522 + }
  523 + }).collect(Collectors.toList());
  524 + PageData<Asset> filteredResult = new PageData<>(filteredAssets,
  525 + nonFilteredResult.getTotalPages(),
  526 + nonFilteredResult.getTotalElements(),
  527 + nonFilteredResult.hasNext());
  528 + return checkNotNull(filteredResult);
  529 + } catch (Exception e) {
  530 + throw handleException(e);
  531 + }
  532 + }
404 533 }
... ...
... ... @@ -37,6 +37,7 @@ import org.thingsboard.common.util.JacksonUtil;
37 37 import org.thingsboard.rule.engine.api.MailService;
38 38 import org.thingsboard.server.common.data.User;
39 39 import org.thingsboard.server.common.data.audit.ActionType;
  40 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
40 41 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
41 42 import org.thingsboard.server.common.data.exception.ThingsboardException;
42 43 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -110,6 +111,8 @@ public class AuthController extends BaseController {
110 111 userCredentials.setPassword(passwordEncoder.encode(newPassword));
111 112 userService.replaceUserCredentials(securityUser.getTenantId(), userCredentials);
112 113
  114 + sendEntityNotificationMsg(getTenantId(), userCredentials.getUserId(), EdgeEventActionType.CREDENTIALS_UPDATED);
  115 +
113 116 eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId()));
114 117 ObjectNode response = JacksonUtil.newObjectNode();
115 118 response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken());
... ... @@ -224,6 +227,8 @@ public class AuthController extends BaseController {
224 227 }
225 228 }
226 229
  230 + sendEntityNotificationMsg(user.getTenantId(), user.getId(), EdgeEventActionType.CREDENTIALS_UPDATED);
  231 +
227 232 JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
228 233 JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
229 234
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.DataConstants;
34 34 import org.thingsboard.server.common.data.Device;
35 35 import org.thingsboard.server.common.data.DeviceInfo;
36 36 import org.thingsboard.server.common.data.DeviceProfile;
  37 +import org.thingsboard.server.common.data.EdgeUtils;
37 38 import org.thingsboard.server.common.data.EntityType;
38 39 import org.thingsboard.server.common.data.EntityView;
39 40 import org.thingsboard.server.common.data.EntityViewInfo;
... ... @@ -50,6 +51,10 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo;
50 51 import org.thingsboard.server.common.data.asset.Asset;
51 52 import org.thingsboard.server.common.data.asset.AssetInfo;
52 53 import org.thingsboard.server.common.data.audit.ActionType;
  54 +import org.thingsboard.server.common.data.edge.Edge;
  55 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
  56 +import org.thingsboard.server.common.data.edge.EdgeEventType;
  57 +import org.thingsboard.server.common.data.edge.EdgeInfo;
53 58 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
54 59 import org.thingsboard.server.common.data.exception.ThingsboardException;
55 60 import org.thingsboard.server.common.data.id.AlarmId;
... ... @@ -58,6 +63,7 @@ import org.thingsboard.server.common.data.id.CustomerId;
58 63 import org.thingsboard.server.common.data.id.DashboardId;
59 64 import org.thingsboard.server.common.data.id.DeviceId;
60 65 import org.thingsboard.server.common.data.id.DeviceProfileId;
  66 +import org.thingsboard.server.common.data.id.EdgeId;
61 67 import org.thingsboard.server.common.data.id.EntityId;
62 68 import org.thingsboard.server.common.data.id.EntityIdFactory;
63 69 import org.thingsboard.server.common.data.id.EntityViewId;
... ... @@ -78,7 +84,9 @@ import org.thingsboard.server.common.data.page.SortOrder;
78 84 import org.thingsboard.server.common.data.page.TimePageLink;
79 85 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
80 86 import org.thingsboard.server.common.data.plugin.ComponentType;
  87 +import org.thingsboard.server.common.data.relation.EntityRelation;
81 88 import org.thingsboard.server.common.data.rule.RuleChain;
  89 +import org.thingsboard.server.common.data.rule.RuleChainType;
82 90 import org.thingsboard.server.common.data.rule.RuleNode;
83 91 import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
84 92 import org.thingsboard.server.common.data.widget.WidgetsBundle;
... ... @@ -94,6 +102,7 @@ import org.thingsboard.server.dao.device.ClaimDevicesService;
94 102 import org.thingsboard.server.dao.device.DeviceCredentialsService;
95 103 import org.thingsboard.server.dao.device.DeviceProfileService;
96 104 import org.thingsboard.server.dao.device.DeviceService;
  105 +import org.thingsboard.server.dao.edge.EdgeService;
97 106 import org.thingsboard.server.dao.entityview.EntityViewService;
98 107 import org.thingsboard.server.dao.exception.DataValidationException;
99 108 import org.thingsboard.server.dao.exception.IncorrectParameterException;
... ... @@ -110,10 +119,14 @@ import org.thingsboard.server.dao.user.UserService;
110 119 import org.thingsboard.server.dao.widget.WidgetTypeService;
111 120 import org.thingsboard.server.dao.widget.WidgetsBundleService;
112 121 import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
  122 +import org.thingsboard.server.gen.transport.TransportProtos;
113 123 import org.thingsboard.server.queue.discovery.PartitionService;
114 124 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
115 125 import org.thingsboard.server.queue.util.TbCoreComponent;
116 126 import org.thingsboard.server.service.component.ComponentDiscoveryService;
  127 +import org.thingsboard.server.service.edge.EdgeNotificationService;
  128 +import org.thingsboard.server.service.edge.rpc.EdgeGrpcService;
  129 +import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
117 130 import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository;
118 131 import org.thingsboard.server.service.profile.TbDeviceProfileCache;
119 132 import org.thingsboard.server.service.queue.TbClusterService;
... ... @@ -244,10 +257,25 @@ public abstract class BaseController {
244 257 @Autowired
245 258 protected LwM2MModelsRepository lwM2MModelsRepository;
246 259
  260 + @Autowired(required = false)
  261 + protected EdgeService edgeService;
  262 +
  263 + @Autowired(required = false)
  264 + protected EdgeNotificationService edgeNotificationService;
  265 +
  266 + @Autowired(required = false)
  267 + protected SyncEdgeService syncEdgeService;
  268 +
  269 + @Autowired(required = false)
  270 + protected EdgeGrpcService edgeGrpcService;
  271 +
247 272 @Value("${server.log_controller_error_stack_trace}")
248 273 @Getter
249 274 private boolean logControllerErrorStackTrace;
250 275
  276 + @Value("${edges.enabled}")
  277 + @Getter
  278 + protected boolean edgesEnabled;
251 279
252 280 @ExceptionHandler(ThingsboardException.class)
253 281 public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
... ... @@ -461,6 +489,9 @@ public abstract class BaseController {
461 489 case ENTITY_VIEW:
462 490 checkEntityViewId(new EntityViewId(entityId.getId()), operation);
463 491 return;
  492 + case EDGE:
  493 + checkEdgeId(new EdgeId(entityId.getId()), operation);
  494 + return;
464 495 case WIDGETS_BUNDLE:
465 496 checkWidgetsBundleId(new WidgetsBundleId(entityId.getId()), operation);
466 497 return;
... ... @@ -622,6 +653,30 @@ public abstract class BaseController {
622 653 }
623 654 }
624 655
  656 + Edge checkEdgeId(EdgeId edgeId, Operation operation) throws ThingsboardException {
  657 + try {
  658 + validateId(edgeId, "Incorrect edgeId " + edgeId);
  659 + Edge edge = edgeService.findEdgeById(getTenantId(), edgeId);
  660 + checkNotNull(edge);
  661 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
  662 + return edge;
  663 + } catch (Exception e) {
  664 + throw handleException(e, false);
  665 + }
  666 + }
  667 +
  668 + EdgeInfo checkEdgeInfoId(EdgeId edgeId, Operation operation) throws ThingsboardException {
  669 + try {
  670 + validateId(edgeId, "Incorrect edgeId " + edgeId);
  671 + EdgeInfo edge = edgeService.findEdgeInfoById(getCurrentUser().getTenantId(), edgeId);
  672 + checkNotNull(edge);
  673 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, edgeId, edge);
  674 + return edge;
  675 + } catch (Exception e) {
  676 + throw handleException(e, false);
  677 + }
  678 + }
  679 +
625 680 DashboardInfo checkDashboardInfoId(DashboardId dashboardId, Operation operation) throws ThingsboardException {
626 681 try {
627 682 validateId(dashboardId, "Incorrect dashboardId " + dashboardId);
... ... @@ -643,19 +698,19 @@ public abstract class BaseController {
643 698 }
644 699 }
645 700
646   - List<ComponentDescriptor> checkComponentDescriptorsByType(ComponentType type) throws ThingsboardException {
  701 + List<ComponentDescriptor> checkComponentDescriptorsByType(ComponentType type, RuleChainType ruleChainType) throws ThingsboardException {
647 702 try {
648 703 log.debug("[{}] Lookup component descriptors", type);
649   - return componentDescriptorService.getComponents(type);
  704 + return componentDescriptorService.getComponents(type, ruleChainType);
650 705 } catch (Exception e) {
651 706 throw handleException(e, false);
652 707 }
653 708 }
654 709
655   - List<ComponentDescriptor> checkComponentDescriptorsByTypes(Set<ComponentType> types) throws ThingsboardException {
  710 + List<ComponentDescriptor> checkComponentDescriptorsByTypes(Set<ComponentType> types, RuleChainType ruleChainType) throws ThingsboardException {
656 711 try {
657 712 log.debug("[{}] Lookup component descriptors", types);
658   - return componentDescriptorService.getComponents(types);
  713 + return componentDescriptorService.getComponents(types, ruleChainType);
659 714 } catch (Exception e) {
660 715 throw handleException(e, false);
661 716 }
... ... @@ -776,6 +831,12 @@ public abstract class BaseController {
776 831 case TIMESERIES_DELETED:
777 832 msgType = DataConstants.TIMESERIES_DELETED;
778 833 break;
  834 + case ASSIGNED_TO_EDGE:
  835 + msgType = DataConstants.ENTITY_ASSIGNED_TO_EDGE;
  836 + break;
  837 + case UNASSIGNED_FROM_EDGE:
  838 + msgType = DataConstants.ENTITY_UNASSIGNED_FROM_EDGE;
  839 + break;
779 840 }
780 841 if (!StringUtils.isEmpty(msgType)) {
781 842 try {
... ... @@ -805,6 +866,16 @@ public abstract class BaseController {
805 866 String strTenantName = extractParameter(String.class, 1, additionalInfo);
806 867 metaData.putValue("assignedToTenantId", strTenantId);
807 868 metaData.putValue("assignedToTenantName", strTenantName);
  869 + } else if (actionType == ActionType.ASSIGNED_TO_EDGE) {
  870 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  871 + String strEdgeName = extractParameter(String.class, 2, additionalInfo);
  872 + metaData.putValue("assignedEdgeId", strEdgeId);
  873 + metaData.putValue("assignedEdgeName", strEdgeName);
  874 + } else if (actionType == ActionType.UNASSIGNED_FROM_EDGE) {
  875 + String strEdgeId = extractParameter(String.class, 1, additionalInfo);
  876 + String strEdgeName = extractParameter(String.class, 2, additionalInfo);
  877 + metaData.putValue("unassignedEdgeId", strEdgeId);
  878 + metaData.putValue("unassignedEdgeName", strEdgeName);
808 879 }
809 880 ObjectNode entityNode;
810 881 if (entity != null) {
... ... @@ -898,6 +969,93 @@ public abstract class BaseController {
898 969 return null;
899 970 }
900 971
  972 + protected void sendRelationNotificationMsg(TenantId tenantId, EntityRelation relation, EdgeEventActionType action) {
  973 + try {
  974 + if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) &&
  975 + !relation.getTo().getEntityType().equals(EntityType.EDGE)) {
  976 + sendNotificationMsgToEdgeService(tenantId, null, null, json.writeValueAsString(relation), EdgeEventType.RELATION, action);
  977 + }
  978 + } catch (Exception e) {
  979 + log.warn("Failed to push relation to core: {}", relation, e);
  980 + }
  981 + }
  982 +
  983 + protected void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List<EdgeId> edgeIds) {
  984 + if (edgeIds != null && !edgeIds.isEmpty()) {
  985 + for (EdgeId edgeId : edgeIds) {
  986 + sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, null, EdgeEventActionType.DELETED);
  987 + }
  988 + }
  989 + }
  990 +
  991 + protected void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) {
  992 + try {
  993 + sendNotificationMsgToEdgeService(tenantId, null, entityId, json.writeValueAsString(customerId), null, action);
  994 + } catch (Exception e) {
  995 + log.warn("Failed to push assign/unassign to/from customer to core: {}", customerId, e);
  996 + }
  997 + }
  998 +
  999 + protected void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) {
  1000 + sendNotificationMsgToEdgeService(tenantId, null, entityId, null, null, action);
  1001 + }
  1002 +
  1003 + protected void sendEntityAssignToEdgeNotificationMsg(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) {
  1004 + sendNotificationMsgToEdgeService(tenantId, edgeId, entityId, null, null, action);
  1005 + }
  1006 +
  1007 + private void sendNotificationMsgToEdgeService(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action) {
  1008 + if (!edgesEnabled) {
  1009 + return;
  1010 + }
  1011 + if (type == null) {
  1012 + if (entityId != null) {
  1013 + type = EdgeUtils.getEdgeEventTypeByEntityType(entityId.getEntityType());
  1014 + } else {
  1015 + log.trace("[{}] entity id and type are null. Ignoring this notification", tenantId);
  1016 + return;
  1017 + }
  1018 + if (type == null) {
  1019 + log.trace("[{}] edge event type is null. Ignoring this notification [{}]", tenantId, entityId);
  1020 + return;
  1021 + }
  1022 + }
  1023 + TransportProtos.EdgeNotificationMsgProto.Builder builder = TransportProtos.EdgeNotificationMsgProto.newBuilder();
  1024 + builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
  1025 + builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
  1026 + builder.setType(type.name());
  1027 + builder.setAction(action.name());
  1028 + if (entityId != null) {
  1029 + builder.setEntityIdMSB(entityId.getId().getMostSignificantBits());
  1030 + builder.setEntityIdLSB(entityId.getId().getLeastSignificantBits());
  1031 + builder.setEntityType(entityId.getEntityType().name());
  1032 + }
  1033 + if (edgeId != null) {
  1034 + builder.setEdgeIdMSB(edgeId.getId().getMostSignificantBits());
  1035 + builder.setEdgeIdLSB(edgeId.getId().getLeastSignificantBits());
  1036 + }
  1037 + if (body != null) {
  1038 + builder.setBody(body);
  1039 + }
  1040 + TransportProtos.EdgeNotificationMsgProto msg = builder.build();
  1041 + log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg);
  1042 + tbClusterService.pushMsgToCore(tenantId, entityId != null ? entityId : tenantId,
  1043 + TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null);
  1044 + }
  1045 +
  1046 + protected List<EdgeId> findRelatedEdgeIds(TenantId tenantId, EntityId entityId) {
  1047 + if (!edgesEnabled) {
  1048 + return null;
  1049 + }
  1050 + List<EdgeId> result = null;
  1051 + try {
  1052 + result = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId).get();
  1053 + } catch (Exception e) {
  1054 + log.error("[{}] can't find related edge ids for entity [{}]", tenantId, entityId, e);
  1055 + }
  1056 + return result;
  1057 + }
  1058 +
901 1059 private void addTimeseries(ObjectNode entityNode, List<TsKvEntry> timeseries) throws Exception {
902 1060 if (timeseries != null && !timeseries.isEmpty()) {
903 1061 ArrayNode result = entityNode.putArray("timeseries");
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import org.apache.commons.lang3.StringUtils;
18 19 import org.springframework.security.access.prepost.PreAuthorize;
19 20 import org.springframework.web.bind.annotation.PathVariable;
20 21 import org.springframework.web.bind.annotation.RequestMapping;
... ... @@ -25,6 +26,7 @@ import org.springframework.web.bind.annotation.RestController;
25 26 import org.thingsboard.server.common.data.exception.ThingsboardException;
26 27 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
27 28 import org.thingsboard.server.common.data.plugin.ComponentType;
  29 +import org.thingsboard.server.common.data.rule.RuleChainType;
28 30 import org.thingsboard.server.queue.util.TbCoreComponent;
29 31
30 32 import java.util.HashSet;
... ... @@ -51,10 +53,11 @@ public class ComponentDescriptorController extends BaseController {
51 53 @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
52 54 @RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET)
53 55 @ResponseBody
54   - public List<ComponentDescriptor> getComponentDescriptorsByType(@PathVariable("componentType") String strComponentType) throws ThingsboardException {
  56 + public List<ComponentDescriptor> getComponentDescriptorsByType(@PathVariable("componentType") String strComponentType,
  57 + @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
55 58 checkParameter("componentType", strComponentType);
56 59 try {
57   - return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType));
  60 + return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType));
58 61 } catch (Exception e) {
59 62 throw handleException(e);
60 63 }
... ... @@ -63,17 +66,28 @@ public class ComponentDescriptorController extends BaseController {
63 66 @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
64 67 @RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
65 68 @ResponseBody
66   - public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes) throws ThingsboardException {
  69 + public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes,
  70 + @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
67 71 checkArrayParameter("componentTypes", strComponentTypes);
68 72 try {
69 73 Set<ComponentType> componentTypes = new HashSet<>();
70 74 for (String strComponentType : strComponentTypes) {
71 75 componentTypes.add(ComponentType.valueOf(strComponentType));
72 76 }
73   - return checkComponentDescriptorsByTypes(componentTypes);
  77 + return checkComponentDescriptorsByTypes(componentTypes, getRuleChainType(strRuleChainType));
74 78 } catch (Exception e) {
75 79 throw handleException(e);
76 80 }
77 81 }
78 82
  83 + private RuleChainType getRuleChainType(String strRuleChainType) {
  84 + RuleChainType ruleChainType;
  85 + if (StringUtils.isEmpty(strRuleChainType)) {
  86 + ruleChainType = RuleChainType.CORE;
  87 + } else {
  88 + ruleChainType = RuleChainType.valueOf(strRuleChainType);
  89 + }
  90 + return ruleChainType;
  91 + }
  92 +
79 93 }
... ...
... ... @@ -31,8 +31,10 @@ import org.springframework.web.bind.annotation.RestController;
31 31 import org.thingsboard.server.common.data.Customer;
32 32 import org.thingsboard.server.common.data.EntityType;
33 33 import org.thingsboard.server.common.data.audit.ActionType;
  34 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
34 35 import org.thingsboard.server.common.data.exception.ThingsboardException;
35 36 import org.thingsboard.server.common.data.id.CustomerId;
  37 +import org.thingsboard.server.common.data.id.EdgeId;
36 38 import org.thingsboard.server.common.data.id.TenantId;
37 39 import org.thingsboard.server.common.data.page.PageData;
38 40 import org.thingsboard.server.common.data.page.PageLink;
... ... @@ -40,6 +42,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
40 42 import org.thingsboard.server.service.security.permission.Operation;
41 43 import org.thingsboard.server.service.security.permission.Resource;
42 44
  45 +import java.util.List;
  46 +
43 47 @RestController
44 48 @TbCoreComponent
45 49 @RequestMapping("/api")
... ... @@ -112,6 +116,10 @@ public class CustomerController extends BaseController {
112 116 savedCustomer.getId(),
113 117 customer.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
114 118
  119 + if (customer.getId() != null) {
  120 + sendEntityNotificationMsg(savedCustomer.getTenantId(), savedCustomer.getId(), EdgeEventActionType.UPDATED);
  121 + }
  122 +
115 123 return savedCustomer;
116 124 } catch (Exception e) {
117 125
... ... @@ -130,12 +138,16 @@ public class CustomerController extends BaseController {
130 138 try {
131 139 CustomerId customerId = new CustomerId(toUUID(strCustomerId));
132 140 Customer customer = checkCustomerId(customerId, Operation.DELETE);
  141 +
  142 + List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), customerId);
  143 +
133 144 customerService.deleteCustomer(getTenantId(), customerId);
134 145
135 146 logEntityAction(customerId, customer,
136 147 customer.getId(),
137 148 ActionType.DELETED, null, strCustomerId);
138 149
  150 + sendDeleteNotificationMsg(getTenantId(), customerId, relatedEdgeIds);
139 151 } catch (Exception e) {
140 152
141 153 logEntityAction(emptyId(EntityType.CUSTOMER),
... ...
... ... @@ -38,20 +38,26 @@ import org.thingsboard.server.common.data.ShortCustomerInfo;
38 38 import org.thingsboard.server.common.data.Tenant;
39 39 import org.thingsboard.server.common.data.User;
40 40 import org.thingsboard.server.common.data.audit.ActionType;
  41 +import org.thingsboard.server.common.data.edge.Edge;
  42 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
41 43 import org.thingsboard.server.common.data.exception.ThingsboardException;
42 44 import org.thingsboard.server.common.data.id.CustomerId;
43 45 import org.thingsboard.server.common.data.id.DashboardId;
  46 +import org.thingsboard.server.common.data.id.EdgeId;
44 47 import org.thingsboard.server.common.data.id.TenantId;
45 48 import org.thingsboard.server.common.data.page.PageData;
46 49 import org.thingsboard.server.common.data.page.PageLink;
47 50 import org.thingsboard.common.util.JacksonUtil;
  51 +import org.thingsboard.server.common.data.page.TimePageLink;
48 52 import org.thingsboard.server.queue.util.TbCoreComponent;
49 53 import org.thingsboard.server.service.security.model.SecurityUser;
50 54 import org.thingsboard.server.service.security.permission.Operation;
51 55 import org.thingsboard.server.service.security.permission.Resource;
52 56
53 57 import java.util.HashSet;
  58 +import java.util.List;
54 59 import java.util.Set;
  60 +import java.util.stream.Collectors;
55 61
56 62 @RestController
57 63 @TbCoreComponent
... ... @@ -122,6 +128,10 @@ public class DashboardController extends BaseController {
122 128 null,
123 129 dashboard.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
124 130
  131 + if (dashboard.getId() != null) {
  132 + sendEntityNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), EdgeEventActionType.UPDATED);
  133 + }
  134 +
125 135 return savedDashboard;
126 136 } catch (Exception e) {
127 137 logEntityAction(emptyId(EntityType.DASHBOARD), dashboard,
... ... @@ -139,12 +149,16 @@ public class DashboardController extends BaseController {
139 149 try {
140 150 DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
141 151 Dashboard dashboard = checkDashboardId(dashboardId, Operation.DELETE);
  152 +
  153 + List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), dashboardId);
  154 +
142 155 dashboardService.deleteDashboard(getCurrentUser().getTenantId(), dashboardId);
143 156
144 157 logEntityAction(dashboardId, dashboard,
145 158 null,
146 159 ActionType.DELETED, null, strDashboardId);
147 160
  161 + sendDeleteNotificationMsg(getTenantId(), dashboardId, relatedEdgeIds);
148 162 } catch (Exception e) {
149 163
150 164 logEntityAction(emptyId(EntityType.DASHBOARD),
... ... @@ -176,6 +190,7 @@ public class DashboardController extends BaseController {
176 190 customerId,
177 191 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, strCustomerId, customer.getName());
178 192
  193 + sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
179 194
180 195 return savedDashboard;
181 196 } catch (Exception e) {
... ... @@ -207,6 +222,8 @@ public class DashboardController extends BaseController {
207 222 customerId,
208 223 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customer.getId().toString(), customer.getName());
209 224
  225 + sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
  226 +
210 227 return savedDashboard;
211 228 } catch (Exception e) {
212 229
... ... @@ -262,6 +279,7 @@ public class DashboardController extends BaseController {
262 279 logEntityAction(dashboardId, savedDashboard,
263 280 customerId,
264 281 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  282 + sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
265 283 }
266 284 for (CustomerId customerId : removedCustomerIds) {
267 285 ShortCustomerInfo customerInfo = dashboard.getAssignedCustomerInfo(customerId);
... ... @@ -269,7 +287,7 @@ public class DashboardController extends BaseController {
269 287 logEntityAction(dashboardId, dashboard,
270 288 customerId,
271 289 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
272   -
  290 + sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
273 291 }
274 292 return savedDashboard;
275 293 }
... ... @@ -313,6 +331,7 @@ public class DashboardController extends BaseController {
313 331 logEntityAction(dashboardId, savedDashboard,
314 332 customerId,
315 333 ActionType.ASSIGNED_TO_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
  334 + sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
316 335 }
317 336 return savedDashboard;
318 337 }
... ... @@ -356,7 +375,7 @@ public class DashboardController extends BaseController {
356 375 logEntityAction(dashboardId, dashboard,
357 376 customerId,
358 377 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDashboardId, customerId.toString(), customerInfo.getTitle());
359   -
  378 + sendEntityAssignToCustomerNotificationMsg(savedDashboard.getTenantId(), savedDashboard.getId(), customerId, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
360 379 }
361 380 return savedDashboard;
362 381 }
... ... @@ -578,4 +597,106 @@ public class DashboardController extends BaseController {
578 597 } catch (Exception e) {}
579 598 return null;
580 599 }
  600 +
  601 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  602 + @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.POST)
  603 + @ResponseBody
  604 + public Dashboard assignDashboardToEdge(@PathVariable("edgeId") String strEdgeId,
  605 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  606 + checkParameter("edgeId", strEdgeId);
  607 + checkParameter(DASHBOARD_ID, strDashboardId);
  608 + try {
  609 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  610 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  611 +
  612 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  613 + checkDashboardId(dashboardId, Operation.ASSIGN_TO_EDGE);
  614 +
  615 + Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  616 +
  617 + logEntityAction(dashboardId, savedDashboard,
  618 + null,
  619 + ActionType.ASSIGNED_TO_EDGE, null, strDashboardId, strEdgeId, edge.getName());
  620 +
  621 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDashboard.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE);
  622 +
  623 + return savedDashboard;
  624 + } catch (Exception e) {
  625 +
  626 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  627 + null,
  628 + ActionType.ASSIGNED_TO_EDGE, e, strDashboardId, strEdgeId);
  629 +
  630 + throw handleException(e);
  631 + }
  632 + }
  633 +
  634 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  635 + @RequestMapping(value = "/edge/{edgeId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
  636 + @ResponseBody
  637 + public Dashboard unassignDashboardFromEdge(@PathVariable("edgeId") String strEdgeId,
  638 + @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
  639 + checkParameter("edgeId", strEdgeId);
  640 + checkParameter(DASHBOARD_ID, strDashboardId);
  641 + try {
  642 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  643 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  644 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  645 + Dashboard dashboard = checkDashboardId(dashboardId, Operation.UNASSIGN_FROM_EDGE);
  646 +
  647 + Dashboard savedDashboard = checkNotNull(dashboardService.unassignDashboardFromEdge(getCurrentUser().getTenantId(), dashboardId, edgeId));
  648 +
  649 + logEntityAction(dashboardId, dashboard,
  650 + null,
  651 + ActionType.UNASSIGNED_FROM_EDGE, null, strDashboardId, strEdgeId, edge.getName());
  652 +
  653 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDashboard.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE);
  654 +
  655 + return savedDashboard;
  656 + } catch (Exception e) {
  657 +
  658 + logEntityAction(emptyId(EntityType.DASHBOARD), null,
  659 + null,
  660 + ActionType.UNASSIGNED_FROM_EDGE, e, strDashboardId, strEdgeId);
  661 +
  662 + throw handleException(e);
  663 + }
  664 + }
  665 +
  666 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  667 + @RequestMapping(value = "/edge/{edgeId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
  668 + @ResponseBody
  669 + public PageData<DashboardInfo> getEdgeDashboards(
  670 + @PathVariable("edgeId") String strEdgeId,
  671 + @RequestParam int pageSize,
  672 + @RequestParam int page,
  673 + @RequestParam(required = false) String textSearch,
  674 + @RequestParam(required = false) String sortProperty,
  675 + @RequestParam(required = false) String sortOrder,
  676 + @RequestParam(required = false) Long startTime,
  677 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
  678 + checkParameter("edgeId", strEdgeId);
  679 + try {
  680 + TenantId tenantId = getCurrentUser().getTenantId();
  681 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  682 + checkEdgeId(edgeId, Operation.READ);
  683 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  684 + PageData<DashboardInfo> nonFilteredResult = dashboardService.findDashboardsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
  685 + List<DashboardInfo> filteredDashboards = nonFilteredResult.getData().stream().filter(dashboardInfo -> {
  686 + try {
  687 + accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, Operation.READ, dashboardInfo.getId(), dashboardInfo);
  688 + return true;
  689 + } catch (ThingsboardException e) {
  690 + return false;
  691 + }
  692 + }).collect(Collectors.toList());
  693 + PageData<DashboardInfo> filteredResult = new PageData<>(filteredDashboards,
  694 + nonFilteredResult.getTotalPages(),
  695 + nonFilteredResult.getTotalElements(),
  696 + nonFilteredResult.hasNext());
  697 + return checkNotNull(filteredResult);
  698 + } catch (Exception e) {
  699 + throw handleException(e);
  700 + }
  701 + }
581 702 }
... ...
... ... @@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.ResponseStatus;
32 32 import org.springframework.web.bind.annotation.RestController;
33 33 import org.springframework.web.context.request.async.DeferredResult;
34 34 import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
  35 +import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
35 36 import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
36 37 import org.thingsboard.server.common.data.ClaimRequest;
37 38 import org.thingsboard.server.common.data.Customer;
... ... @@ -43,14 +44,18 @@ import org.thingsboard.server.common.data.EntityType;
43 44 import org.thingsboard.server.common.data.Tenant;
44 45 import org.thingsboard.server.common.data.audit.ActionType;
45 46 import org.thingsboard.server.common.data.device.DeviceSearchQuery;
  47 +import org.thingsboard.server.common.data.edge.Edge;
  48 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
46 49 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
47 50 import org.thingsboard.server.common.data.exception.ThingsboardException;
48 51 import org.thingsboard.server.common.data.id.CustomerId;
49 52 import org.thingsboard.server.common.data.id.DeviceId;
50 53 import org.thingsboard.server.common.data.id.DeviceProfileId;
  54 +import org.thingsboard.server.common.data.id.EdgeId;
51 55 import org.thingsboard.server.common.data.id.TenantId;
52 56 import org.thingsboard.server.common.data.page.PageData;
53 57 import org.thingsboard.server.common.data.page.PageLink;
  58 +import org.thingsboard.server.common.data.page.TimePageLink;
54 59 import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
55 60 import org.thingsboard.server.common.data.security.DeviceCredentials;
56 61 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -72,6 +77,8 @@ import java.util.ArrayList;
72 77 import java.util.List;
73 78 import java.util.stream.Collectors;
74 79
  80 +import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
  81 +
75 82 @RestController
76 83 @TbCoreComponent
77 84 @RequestMapping("/api")
... ... @@ -125,6 +132,10 @@ public class DeviceController extends BaseController {
125 132 tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(),
126 133 device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
127 134
  135 + if (device.getId() != null) {
  136 + sendEntityNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(), EdgeEventActionType.UPDATED);
  137 + }
  138 +
128 139 logEntityAction(savedDevice.getId(), savedDevice,
129 140 savedDevice.getCustomerId(),
130 141 device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
... ... @@ -150,6 +161,9 @@ public class DeviceController extends BaseController {
150 161 try {
151 162 DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
152 163 Device device = checkDeviceId(deviceId, Operation.DELETE);
  164 +
  165 + List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), deviceId);
  166 +
153 167 deviceService.deleteDevice(getCurrentUser().getTenantId(), deviceId);
154 168
155 169 tbClusterService.onDeviceDeleted(device, null);
... ... @@ -159,6 +173,8 @@ public class DeviceController extends BaseController {
159 173 device.getCustomerId(),
160 174 ActionType.DELETED, null, strDeviceId);
161 175
  176 + sendDeleteNotificationMsg(getTenantId(), deviceId, relatedEdgeIds);
  177 +
162 178 deviceStateService.onDeviceDeleted(device);
163 179 } catch (Exception e) {
164 180 logEntityAction(emptyId(EntityType.DEVICE),
... ... @@ -189,6 +205,9 @@ public class DeviceController extends BaseController {
189 205 savedDevice.getCustomerId(),
190 206 ActionType.ASSIGNED_TO_CUSTOMER, null, strDeviceId, strCustomerId, customer.getName());
191 207
  208 + sendEntityAssignToCustomerNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(),
  209 + customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
  210 +
192 211 return savedDevice;
193 212 } catch (Exception e) {
194 213 logEntityAction(emptyId(EntityType.DEVICE), null,
... ... @@ -217,6 +236,9 @@ public class DeviceController extends BaseController {
217 236 device.getCustomerId(),
218 237 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strDeviceId, customer.getId().toString(), customer.getName());
219 238
  239 + sendEntityAssignToCustomerNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(),
  240 + customer.getId(), EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
  241 +
220 242 return savedDevice;
221 243 } catch (Exception e) {
222 244 logEntityAction(emptyId(EntityType.DEVICE), null,
... ... @@ -280,6 +302,9 @@ public class DeviceController extends BaseController {
280 302 Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS);
281 303 DeviceCredentials result = checkNotNull(deviceCredentialsService.updateDeviceCredentials(getCurrentUser().getTenantId(), deviceCredentials));
282 304 tbClusterService.pushMsgToCore(new DeviceCredentialsUpdateNotificationMsg(getCurrentUser().getTenantId(), deviceCredentials.getDeviceId(), result), null);
  305 +
  306 + sendEntityNotificationMsg(getTenantId(), device.getId(), EdgeEventActionType.CREDENTIALS_UPDATED);
  307 +
283 308 logEntityAction(device.getId(), device,
284 309 device.getCustomerId(),
285 310 ActionType.CREDENTIALS_UPDATED, null, deviceCredentials);
... ... @@ -631,4 +656,115 @@ public class DeviceController extends BaseController {
631 656 metaData.putValue("assignedFromTenantName", tenant.getName());
632 657 return metaData;
633 658 }
  659 +
  660 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  661 + @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
  662 + @ResponseBody
  663 + public Device assignDeviceToEdge(@PathVariable(EDGE_ID) String strEdgeId,
  664 + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
  665 + checkParameter(EDGE_ID, strEdgeId);
  666 + checkParameter(DEVICE_ID, strDeviceId);
  667 + try {
  668 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  669 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  670 +
  671 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  672 + checkDeviceId(deviceId, Operation.ASSIGN_TO_EDGE);
  673 +
  674 + Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
  675 +
  676 + tbClusterService.pushMsgToCore(new DeviceEdgeUpdateMsg(savedDevice.getTenantId(),
  677 + savedDevice.getId(), edgeId), null);
  678 +
  679 + logEntityAction(deviceId, savedDevice,
  680 + savedDevice.getCustomerId(),
  681 + ActionType.ASSIGNED_TO_EDGE, null, strDeviceId, strEdgeId, edge.getName());
  682 +
  683 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDevice.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE);
  684 +
  685 + return savedDevice;
  686 + } catch (Exception e) {
  687 + logEntityAction(emptyId(EntityType.DEVICE), null,
  688 + null,
  689 + ActionType.ASSIGNED_TO_EDGE, e, strDeviceId, strEdgeId);
  690 + throw handleException(e);
  691 + }
  692 + }
  693 +
  694 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  695 + @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE)
  696 + @ResponseBody
  697 + public Device unassignDeviceFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
  698 + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
  699 + checkParameter(EDGE_ID, strEdgeId);
  700 + checkParameter(DEVICE_ID, strDeviceId);
  701 + try {
  702 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  703 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  704 +
  705 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  706 + Device device = checkDeviceId(deviceId, Operation.UNASSIGN_FROM_EDGE);
  707 +
  708 + Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
  709 +
  710 + tbClusterService.pushMsgToCore(new DeviceEdgeUpdateMsg(savedDevice.getTenantId(),
  711 + savedDevice.getId(), null), null);
  712 +
  713 + logEntityAction(deviceId, device,
  714 + device.getCustomerId(),
  715 + ActionType.UNASSIGNED_FROM_EDGE, null, strDeviceId, strEdgeId, edge.getName());
  716 +
  717 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedDevice.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE);
  718 +
  719 + return savedDevice;
  720 + } catch (Exception e) {
  721 + logEntityAction(emptyId(EntityType.DEVICE), null,
  722 + null,
  723 + ActionType.UNASSIGNED_FROM_EDGE, e, strDeviceId, strEdgeId);
  724 + throw handleException(e);
  725 + }
  726 + }
  727 +
  728 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  729 + @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
  730 + @ResponseBody
  731 + public PageData<Device> getEdgeDevices(
  732 + @PathVariable(EDGE_ID) String strEdgeId,
  733 + @RequestParam int pageSize,
  734 + @RequestParam int page,
  735 + @RequestParam(required = false) String type,
  736 + @RequestParam(required = false) String textSearch,
  737 + @RequestParam(required = false) String sortProperty,
  738 + @RequestParam(required = false) String sortOrder,
  739 + @RequestParam(required = false) Long startTime,
  740 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
  741 + checkParameter(EDGE_ID, strEdgeId);
  742 + try {
  743 + TenantId tenantId = getCurrentUser().getTenantId();
  744 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  745 + checkEdgeId(edgeId, Operation.READ);
  746 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  747 + PageData<Device> nonFilteredResult;
  748 + if (type != null && type.trim().length() > 0) {
  749 + nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink);
  750 + } else {
  751 + nonFilteredResult = deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
  752 + }
  753 + List<Device> filteredDevices = nonFilteredResult.getData().stream().filter(device -> {
  754 + try {
  755 + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, Operation.READ, device.getId(), device);
  756 + return true;
  757 + } catch (ThingsboardException e) {
  758 + return false;
  759 + }
  760 + }).collect(Collectors.toList());
  761 + PageData<Device> filteredResult = new PageData<>(filteredDevices,
  762 + nonFilteredResult.getTotalPages(),
  763 + nonFilteredResult.getTotalElements(),
  764 + nonFilteredResult.hasNext());
  765 + return checkNotNull(filteredResult);
  766 + } catch (Exception e) {
  767 + throw handleException(e);
  768 + }
  769 + }
634 770 }
... ...
... ... @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
32 32 import org.thingsboard.server.common.data.DeviceProfileInfo;
33 33 import org.thingsboard.server.common.data.EntityType;
34 34 import org.thingsboard.server.common.data.audit.ActionType;
  35 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
35 36 import org.thingsboard.server.common.data.exception.ThingsboardException;
36 37 import org.thingsboard.server.common.data.id.DeviceProfileId;
37 38 import org.thingsboard.server.common.data.page.PageData;
... ... @@ -153,6 +154,9 @@ public class DeviceProfileController extends BaseController {
153 154 null,
154 155 created ? ActionType.ADDED : ActionType.UPDATED, null);
155 156
  157 + sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(),
  158 + deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
  159 +
156 160 return savedDeviceProfile;
157 161 } catch (Exception e) {
158 162 logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile,
... ... @@ -178,6 +182,7 @@ public class DeviceProfileController extends BaseController {
178 182 null,
179 183 ActionType.DELETED, null, strDeviceProfileId);
180 184
  185 + sendEntityNotificationMsg(getTenantId(), deviceProfile.getId(), EdgeEventActionType.DELETED);
181 186 } catch (Exception e) {
182 187 logEntityAction(emptyId(EntityType.DEVICE_PROFILE),
183 188 null,
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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.EdgeEventActionType;
  35 +import org.thingsboard.server.common.data.edge.EdgeInfo;
  36 +import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
  37 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
  38 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  39 +import org.thingsboard.server.common.data.id.CustomerId;
  40 +import org.thingsboard.server.common.data.id.EdgeId;
  41 +import org.thingsboard.server.common.data.id.RuleChainId;
  42 +import org.thingsboard.server.common.data.id.TenantId;
  43 +import org.thingsboard.server.common.data.page.PageData;
  44 +import org.thingsboard.server.common.data.page.PageLink;
  45 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
  46 +import org.thingsboard.server.common.data.rule.RuleChain;
  47 +import org.thingsboard.server.common.data.security.Authority;
  48 +import org.thingsboard.server.dao.exception.DataValidationException;
  49 +import org.thingsboard.server.dao.exception.IncorrectParameterException;
  50 +import org.thingsboard.server.dao.model.ModelConstants;
  51 +import org.thingsboard.server.queue.util.TbCoreComponent;
  52 +import org.thingsboard.server.service.edge.rpc.EdgeGrpcSession;
  53 +import org.thingsboard.server.service.security.model.SecurityUser;
  54 +import org.thingsboard.server.service.security.permission.Operation;
  55 +import org.thingsboard.server.service.security.permission.Resource;
  56 +
  57 +import java.util.ArrayList;
  58 +import java.util.List;
  59 +import java.util.stream.Collectors;
  60 +
  61 +@RestController
  62 +@TbCoreComponent
  63 +@RequestMapping("/api")
  64 +public class EdgeController extends BaseController {
  65 +
  66 + public static final String EDGE_ID = "edgeId";
  67 +
  68 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  69 + @RequestMapping(value = "/edges/enabled", method = RequestMethod.GET)
  70 + @ResponseBody
  71 + public boolean isEdgesSupportEnabled() {
  72 + return edgesEnabled;
  73 + }
  74 +
  75 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  76 + @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.GET)
  77 + @ResponseBody
  78 + public Edge getEdgeById(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  79 + checkParameter(EDGE_ID, strEdgeId);
  80 + try {
  81 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  82 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  83 + if (Authority.CUSTOMER_USER.equals(getCurrentUser().getAuthority())) {
  84 + cleanUpSensitiveData(edge);
  85 + }
  86 + return edge;
  87 + } catch (Exception e) {
  88 + throw handleException(e);
  89 + }
  90 + }
  91 +
  92 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  93 + @RequestMapping(value = "/edge/info/{edgeId}", method = RequestMethod.GET)
  94 + @ResponseBody
  95 + public EdgeInfo getEdgeInfoById(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  96 + checkParameter(EDGE_ID, strEdgeId);
  97 + try {
  98 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  99 + EdgeInfo edgeInfo = checkEdgeInfoId(edgeId, Operation.READ);
  100 + if (Authority.CUSTOMER_USER.equals(getCurrentUser().getAuthority())) {
  101 + cleanUpSensitiveData(edgeInfo);
  102 + }
  103 + return edgeInfo;
  104 + } catch (Exception e) {
  105 + throw handleException(e);
  106 + }
  107 + }
  108 +
  109 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  110 + @RequestMapping(value = "/edge", method = RequestMethod.POST)
  111 + @ResponseBody
  112 + public Edge saveEdge(@RequestBody Edge edge) throws ThingsboardException {
  113 + try {
  114 + TenantId tenantId = getCurrentUser().getTenantId();
  115 + edge.setTenantId(tenantId);
  116 + boolean created = edge.getId() == null;
  117 +
  118 + RuleChain edgeTemplateRootRuleChain = null;
  119 + if (created) {
  120 + edgeTemplateRootRuleChain = ruleChainService.getEdgeTemplateRootRuleChain(tenantId);
  121 + if (edgeTemplateRootRuleChain == null) {
  122 + throw new DataValidationException("Root edge rule chain is not available!");
  123 + }
  124 + }
  125 +
  126 + Operation operation = created ? Operation.CREATE : Operation.WRITE;
  127 +
  128 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
  129 + edge.getId(), edge);
  130 +
  131 + Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
  132 +
  133 + if (created) {
  134 + ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), savedEdge.getId());
  135 + edgeNotificationService.setEdgeRootRuleChain(tenantId, savedEdge, edgeTemplateRootRuleChain.getId());
  136 + edgeService.assignDefaultRuleChainsToEdge(tenantId, savedEdge.getId());
  137 + }
  138 +
  139 + tbClusterService.onEntityStateChange(savedEdge.getTenantId(), savedEdge.getId(),
  140 + created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  141 +
  142 + logEntityAction(savedEdge.getId(), savedEdge, null, created ? ActionType.ADDED : ActionType.UPDATED, null);
  143 + return savedEdge;
  144 + } catch (Exception e) {
  145 + logEntityAction(emptyId(EntityType.EDGE), edge,
  146 + null, edge.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
  147 + throw handleException(e);
  148 + }
  149 + }
  150 +
  151 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  152 + @RequestMapping(value = "/edge/{edgeId}", method = RequestMethod.DELETE)
  153 + @ResponseStatus(value = HttpStatus.OK)
  154 + public void deleteEdge(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  155 + checkParameter(EDGE_ID, strEdgeId);
  156 + try {
  157 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  158 + Edge edge = checkEdgeId(edgeId, Operation.DELETE);
  159 + edgeService.deleteEdge(getTenantId(), edgeId);
  160 +
  161 + tbClusterService.onEntityStateChange(getTenantId(), edgeId,
  162 + ComponentLifecycleEvent.DELETED);
  163 +
  164 + logEntityAction(edgeId, edge,
  165 + null,
  166 + ActionType.DELETED, null, strEdgeId);
  167 +
  168 + } catch (Exception e) {
  169 +
  170 + logEntityAction(emptyId(EntityType.EDGE),
  171 + null,
  172 + null,
  173 + ActionType.DELETED, e, strEdgeId);
  174 +
  175 + throw handleException(e);
  176 + }
  177 + }
  178 +
  179 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  180 + @RequestMapping(value = "/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
  181 + @ResponseBody
  182 + public PageData<Edge> getEdges(@RequestParam int pageSize,
  183 + @RequestParam int page,
  184 + @RequestParam(required = false) String textSearch,
  185 + @RequestParam(required = false) String sortProperty,
  186 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  187 + try {
  188 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  189 + TenantId tenantId = getCurrentUser().getTenantId();
  190 + return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink));
  191 + } catch (Exception e) {
  192 + throw handleException(e);
  193 + }
  194 + }
  195 +
  196 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  197 + @RequestMapping(value = "/customer/{customerId}/edge/{edgeId}", method = RequestMethod.POST)
  198 + @ResponseBody
  199 + public Edge assignEdgeToCustomer(@PathVariable("customerId") String strCustomerId,
  200 + @PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  201 + checkParameter("customerId", strCustomerId);
  202 + checkParameter(EDGE_ID, strEdgeId);
  203 + try {
  204 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  205 + Customer customer = checkCustomerId(customerId, Operation.READ);
  206 +
  207 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  208 + checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER);
  209 +
  210 + Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, customerId));
  211 +
  212 + tbClusterService.onEntityStateChange(getTenantId(), edgeId,
  213 + ComponentLifecycleEvent.UPDATED);
  214 +
  215 + logEntityAction(edgeId, savedEdge,
  216 + savedEdge.getCustomerId(),
  217 + ActionType.ASSIGNED_TO_CUSTOMER, null, strEdgeId, strCustomerId, customer.getName());
  218 +
  219 + sendEntityAssignToCustomerNotificationMsg(savedEdge.getTenantId(), savedEdge.getId(),
  220 + customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
  221 +
  222 + return savedEdge;
  223 + } catch (Exception e) {
  224 + logEntityAction(emptyId(EntityType.EDGE), null,
  225 + null,
  226 + ActionType.ASSIGNED_TO_CUSTOMER, e, strEdgeId, strCustomerId);
  227 + throw handleException(e);
  228 + }
  229 + }
  230 +
  231 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  232 + @RequestMapping(value = "/customer/edge/{edgeId}", method = RequestMethod.DELETE)
  233 + @ResponseBody
  234 + public Edge unassignEdgeFromCustomer(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  235 + checkParameter(EDGE_ID, strEdgeId);
  236 + try {
  237 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  238 + Edge edge = checkEdgeId(edgeId, Operation.UNASSIGN_FROM_CUSTOMER);
  239 + if (edge.getCustomerId() == null || edge.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) {
  240 + throw new IncorrectParameterException("Edge isn't assigned to any customer!");
  241 + }
  242 + Customer customer = checkCustomerId(edge.getCustomerId(), Operation.READ);
  243 +
  244 + Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(getCurrentUser().getTenantId(), edgeId));
  245 +
  246 + tbClusterService.onEntityStateChange(getTenantId(), edgeId,
  247 + ComponentLifecycleEvent.UPDATED);
  248 +
  249 + logEntityAction(edgeId, edge,
  250 + edge.getCustomerId(),
  251 + ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEdgeId, customer.getId().toString(), customer.getName());
  252 +
  253 + sendEntityAssignToCustomerNotificationMsg(savedEdge.getTenantId(), savedEdge.getId(),
  254 + customer.getId(), EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
  255 +
  256 + return savedEdge;
  257 + } catch (Exception e) {
  258 + logEntityAction(emptyId(EntityType.EDGE), null,
  259 + null,
  260 + ActionType.UNASSIGNED_FROM_CUSTOMER, e, strEdgeId);
  261 + throw handleException(e);
  262 + }
  263 + }
  264 +
  265 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  266 + @RequestMapping(value = "/customer/public/edge/{edgeId}", method = RequestMethod.POST)
  267 + @ResponseBody
  268 + public Edge assignEdgeToPublicCustomer(@PathVariable(EDGE_ID) String strEdgeId) throws ThingsboardException {
  269 + checkParameter(EDGE_ID, strEdgeId);
  270 + try {
  271 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  272 + Edge edge = checkEdgeId(edgeId, Operation.ASSIGN_TO_CUSTOMER);
  273 + Customer publicCustomer = customerService.findOrCreatePublicCustomer(edge.getTenantId());
  274 + Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(getCurrentUser().getTenantId(), edgeId, publicCustomer.getId()));
  275 +
  276 + tbClusterService.onEntityStateChange(getTenantId(), edgeId,
  277 + ComponentLifecycleEvent.UPDATED);
  278 +
  279 + logEntityAction(edgeId, savedEdge,
  280 + savedEdge.getCustomerId(),
  281 + ActionType.ASSIGNED_TO_CUSTOMER, null, strEdgeId, publicCustomer.getId().toString(), publicCustomer.getName());
  282 +
  283 + return savedEdge;
  284 + } catch (Exception e) {
  285 + logEntityAction(emptyId(EntityType.EDGE), null,
  286 + null,
  287 + ActionType.ASSIGNED_TO_CUSTOMER, e, strEdgeId);
  288 + throw handleException(e);
  289 + }
  290 + }
  291 +
  292 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  293 + @RequestMapping(value = "/tenant/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
  294 + @ResponseBody
  295 + public PageData<Edge> getTenantEdges(
  296 + @RequestParam int pageSize,
  297 + @RequestParam int page,
  298 + @RequestParam(required = false) String type,
  299 + @RequestParam(required = false) String textSearch,
  300 + @RequestParam(required = false) String sortProperty,
  301 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  302 + try {
  303 + TenantId tenantId = getCurrentUser().getTenantId();
  304 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  305 + if (type != null && type.trim().length() > 0) {
  306 + return checkNotNull(edgeService.findEdgesByTenantIdAndType(tenantId, type, pageLink));
  307 + } else {
  308 + return checkNotNull(edgeService.findEdgesByTenantId(tenantId, pageLink));
  309 + }
  310 + } catch (Exception e) {
  311 + throw handleException(e);
  312 + }
  313 + }
  314 +
  315 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  316 + @RequestMapping(value = "/tenant/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  317 + @ResponseBody
  318 + public PageData<EdgeInfo> getTenantEdgeInfos(
  319 + @RequestParam int pageSize,
  320 + @RequestParam int page,
  321 + @RequestParam(required = false) String type,
  322 + @RequestParam(required = false) String textSearch,
  323 + @RequestParam(required = false) String sortProperty,
  324 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  325 + try {
  326 + TenantId tenantId = getCurrentUser().getTenantId();
  327 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  328 + if (type != null && type.trim().length() > 0) {
  329 + return checkNotNull(edgeService.findEdgeInfosByTenantIdAndType(tenantId, type, pageLink));
  330 + } else {
  331 + return checkNotNull(edgeService.findEdgeInfosByTenantId(tenantId, pageLink));
  332 + }
  333 + } catch (Exception e) {
  334 + throw handleException(e);
  335 + }
  336 + }
  337 +
  338 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  339 + @RequestMapping(value = "/tenant/edges", params = {"edgeName"}, method = RequestMethod.GET)
  340 + @ResponseBody
  341 + public Edge getTenantEdge(@RequestParam String edgeName) throws ThingsboardException {
  342 + try {
  343 + TenantId tenantId = getCurrentUser().getTenantId();
  344 + return checkNotNull(edgeService.findEdgeByTenantIdAndName(tenantId, edgeName));
  345 + } catch (Exception e) {
  346 + throw handleException(e);
  347 + }
  348 + }
  349 +
  350 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  351 + @RequestMapping(value = "/edge/{edgeId}/{ruleChainId}/root", method = RequestMethod.POST)
  352 + @ResponseBody
  353 + public Edge setRootRuleChain(@PathVariable(EDGE_ID) String strEdgeId,
  354 + @PathVariable("ruleChainId") String strRuleChainId) throws ThingsboardException {
  355 + checkParameter(EDGE_ID, strEdgeId);
  356 + checkParameter("ruleChainId", strRuleChainId);
  357 + try {
  358 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  359 + checkRuleChain(ruleChainId, Operation.WRITE);
  360 +
  361 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  362 + Edge edge = checkEdgeId(edgeId, Operation.WRITE);
  363 + accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, Operation.WRITE,
  364 + edge.getId(), edge);
  365 +
  366 + Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(getTenantId(), edge, ruleChainId);
  367 +
  368 + tbClusterService.onEntityStateChange(updatedEdge.getTenantId(), updatedEdge.getId(), ComponentLifecycleEvent.UPDATED);
  369 +
  370 + logEntityAction(updatedEdge.getId(), updatedEdge, null, ActionType.UPDATED, null);
  371 +
  372 + return updatedEdge;
  373 + } catch (Exception e) {
  374 + logEntityAction(emptyId(EntityType.EDGE),
  375 + null,
  376 + null,
  377 + ActionType.UPDATED, e, strEdgeId);
  378 + throw handleException(e);
  379 + }
  380 + }
  381 +
  382 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  383 + @RequestMapping(value = "/customer/{customerId}/edges", params = {"pageSize", "page"}, method = RequestMethod.GET)
  384 + @ResponseBody
  385 + public PageData<Edge> getCustomerEdges(
  386 + @PathVariable("customerId") String strCustomerId,
  387 + @RequestParam int pageSize,
  388 + @RequestParam int page,
  389 + @RequestParam(required = false) String type,
  390 + @RequestParam(required = false) String textSearch,
  391 + @RequestParam(required = false) String sortProperty,
  392 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  393 + checkParameter("customerId", strCustomerId);
  394 + try {
  395 + SecurityUser user = getCurrentUser();
  396 + TenantId tenantId = user.getTenantId();
  397 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  398 + checkCustomerId(customerId, Operation.READ);
  399 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  400 + PageData<Edge> result;
  401 + if (type != null && type.trim().length() > 0) {
  402 + result = edgeService.findEdgesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink);
  403 + } else {
  404 + result = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
  405 + }
  406 + if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
  407 + for (Edge edge : result.getData()) {
  408 + cleanUpSensitiveData(edge);
  409 + }
  410 + }
  411 + return checkNotNull(result);
  412 + } catch (Exception e) {
  413 + throw handleException(e);
  414 + }
  415 + }
  416 +
  417 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  418 + @RequestMapping(value = "/customer/{customerId}/edgeInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
  419 + @ResponseBody
  420 + public PageData<EdgeInfo> getCustomerEdgeInfos(
  421 + @PathVariable("customerId") String strCustomerId,
  422 + @RequestParam int pageSize,
  423 + @RequestParam int page,
  424 + @RequestParam(required = false) String type,
  425 + @RequestParam(required = false) String textSearch,
  426 + @RequestParam(required = false) String sortProperty,
  427 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  428 + checkParameter("customerId", strCustomerId);
  429 + try {
  430 + SecurityUser user = getCurrentUser();
  431 + TenantId tenantId = user.getTenantId();
  432 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  433 + checkCustomerId(customerId, Operation.READ);
  434 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  435 + PageData<EdgeInfo> result;
  436 + if (type != null && type.trim().length() > 0) {
  437 + result = edgeService.findEdgeInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink);
  438 + } else {
  439 + result = edgeService.findEdgeInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink);
  440 + }
  441 + if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
  442 + for (Edge edge : result.getData()) {
  443 + cleanUpSensitiveData(edge);
  444 + }
  445 + }
  446 + return checkNotNull(result);
  447 + } catch (Exception e) {
  448 + throw handleException(e);
  449 + }
  450 + }
  451 +
  452 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  453 + @RequestMapping(value = "/edges", params = {"edgeIds"}, method = RequestMethod.GET)
  454 + @ResponseBody
  455 + public List<Edge> getEdgesByIds(
  456 + @RequestParam("edgeIds") String[] strEdgeIds) throws ThingsboardException {
  457 + checkArrayParameter("edgeIds", strEdgeIds);
  458 + try {
  459 + SecurityUser user = getCurrentUser();
  460 + TenantId tenantId = user.getTenantId();
  461 + CustomerId customerId = user.getCustomerId();
  462 + List<EdgeId> edgeIds = new ArrayList<>();
  463 + for (String strEdgeId : strEdgeIds) {
  464 + edgeIds.add(new EdgeId(toUUID(strEdgeId)));
  465 + }
  466 + ListenableFuture<List<Edge>> edgesFuture;
  467 + if (customerId == null || customerId.isNullUid()) {
  468 + edgesFuture = edgeService.findEdgesByTenantIdAndIdsAsync(tenantId, edgeIds);
  469 + } else {
  470 + edgesFuture = edgeService.findEdgesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, edgeIds);
  471 + }
  472 + List<Edge> edges = edgesFuture.get();
  473 + if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
  474 + for (Edge edge : edges) {
  475 + cleanUpSensitiveData(edge);
  476 + }
  477 + }
  478 + return checkNotNull(edges);
  479 + } catch (Exception e) {
  480 + throw handleException(e);
  481 + }
  482 + }
  483 +
  484 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  485 + @RequestMapping(value = "/edges", method = RequestMethod.POST)
  486 + @ResponseBody
  487 + public List<Edge> findByQuery(@RequestBody EdgeSearchQuery query) throws ThingsboardException {
  488 + checkNotNull(query);
  489 + checkNotNull(query.getParameters());
  490 + checkNotNull(query.getEdgeTypes());
  491 + checkEntityId(query.getParameters().getEntityId(), Operation.READ);
  492 + try {
  493 + SecurityUser user = getCurrentUser();
  494 + TenantId tenantId = user.getTenantId();
  495 + List<Edge> edges = checkNotNull(edgeService.findEdgesByQuery(tenantId, query).get());
  496 + edges = edges.stream().filter(edge -> {
  497 + try {
  498 + accessControlService.checkPermission(user, Resource.EDGE, Operation.READ, edge.getId(), edge);
  499 + return true;
  500 + } catch (ThingsboardException e) {
  501 + return false;
  502 + }
  503 + }).collect(Collectors.toList());
  504 + if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
  505 + for (Edge edge : edges) {
  506 + cleanUpSensitiveData(edge);
  507 + }
  508 + }
  509 + return edges;
  510 + } catch (Exception e) {
  511 + throw handleException(e);
  512 + }
  513 + }
  514 +
  515 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  516 + @RequestMapping(value = "/edge/types", method = RequestMethod.GET)
  517 + @ResponseBody
  518 + public List<EntitySubtype> getEdgeTypes() throws ThingsboardException {
  519 + try {
  520 + SecurityUser user = getCurrentUser();
  521 + TenantId tenantId = user.getTenantId();
  522 + ListenableFuture<List<EntitySubtype>> edgeTypes = edgeService.findEdgeTypesByTenantId(tenantId);
  523 + return checkNotNull(edgeTypes.get());
  524 + } catch (Exception e) {
  525 + throw handleException(e);
  526 + }
  527 + }
  528 +
  529 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  530 + @RequestMapping(value = "/edge/sync/{edgeId}", method = RequestMethod.POST)
  531 + public void syncEdge(@PathVariable("edgeId") String strEdgeId) throws ThingsboardException {
  532 + checkParameter("edgeId", strEdgeId);
  533 + try {
  534 + if (isEdgesEnabled()) {
  535 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  536 + edgeId = checkNotNull(edgeId);
  537 + SecurityUser user = getCurrentUser();
  538 + TenantId tenantId = user.getTenantId();
  539 + EdgeGrpcSession session = edgeGrpcService.getEdgeGrpcSessionById(tenantId, edgeId);
  540 + Edge edge = session.getEdge();
  541 + syncEdgeService.sync(tenantId, edge);
  542 + } else {
  543 + throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL);
  544 + }
  545 + } catch (Exception e) {
  546 + throw handleException(e);
  547 + }
  548 + }
  549 +
  550 + @RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
  551 + @ResponseBody
  552 + public Object checkInstance(@RequestBody Object request) throws ThingsboardException {
  553 + try {
  554 + return edgeService.checkInstance(request);
  555 + } catch (Exception e) {
  556 + throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
  557 + }
  558 + }
  559 +
  560 + @RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
  561 + @ResponseBody
  562 + public Object activateInstance(@RequestParam String licenseSecret,
  563 + @RequestParam String releaseDate) throws ThingsboardException {
  564 + try {
  565 + return edgeService.activateInstance(licenseSecret, releaseDate);
  566 + } catch (Exception e) {
  567 + throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
  568 + }
  569 + }
  570 +
  571 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  572 + @RequestMapping(value = "/edge/missingToRelatedRuleChains/{edgeId}", method = RequestMethod.GET)
  573 + @ResponseBody
  574 + public String findMissingToRelatedRuleChains(@PathVariable("edgeId") String strEdgeId) throws ThingsboardException {
  575 + try {
  576 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  577 + edgeId = checkNotNull(edgeId);
  578 + SecurityUser user = getCurrentUser();
  579 + TenantId tenantId = user.getTenantId();
  580 + return edgeService.findMissingToRelatedRuleChains(tenantId, edgeId);
  581 + } catch (Exception e) {
  582 + throw handleException(e);
  583 + }
  584 + }
  585 +
  586 + private void cleanUpSensitiveData(Edge edge) {
  587 + edge.setEdgeLicenseKey(null);
  588 + edge.setRoutingKey(null);
  589 + edge.setSecret(null);
  590 + edge.setCloudEndpoint(null);
  591 + edge.setRootRuleChainId(null);
  592 + }
  593 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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 lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.security.access.prepost.PreAuthorize;
  21 +import org.springframework.web.bind.annotation.PathVariable;
  22 +import org.springframework.web.bind.annotation.RequestMapping;
  23 +import org.springframework.web.bind.annotation.RequestMethod;
  24 +import org.springframework.web.bind.annotation.RequestParam;
  25 +import org.springframework.web.bind.annotation.ResponseBody;
  26 +import org.springframework.web.bind.annotation.RestController;
  27 +import org.thingsboard.server.common.data.edge.EdgeEvent;
  28 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  29 +import org.thingsboard.server.common.data.id.EdgeId;
  30 +import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.page.PageData;
  32 +import org.thingsboard.server.common.data.page.TimePageLink;
  33 +import org.thingsboard.server.dao.edge.EdgeEventService;
  34 +import org.thingsboard.server.queue.util.TbCoreComponent;
  35 +import org.thingsboard.server.service.security.permission.Operation;
  36 +
  37 +@Slf4j
  38 +@RestController
  39 +@TbCoreComponent
  40 +@RequestMapping("/api")
  41 +public class EdgeEventController extends BaseController {
  42 +
  43 + @Autowired
  44 + private EdgeEventService edgeEventService;
  45 +
  46 + public static final String EDGE_ID = "edgeId";
  47 +
  48 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  49 + @RequestMapping(value = "/edge/{edgeId}/events", method = RequestMethod.GET)
  50 + @ResponseBody
  51 + public PageData<EdgeEvent> getEdgeEvents(
  52 + @PathVariable(EDGE_ID) String strEdgeId,
  53 + @RequestParam int pageSize,
  54 + @RequestParam int page,
  55 + @RequestParam(required = false) String textSearch,
  56 + @RequestParam(required = false) String sortProperty,
  57 + @RequestParam(required = false) String sortOrder,
  58 + @RequestParam(required = false) Long startTime,
  59 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
  60 + checkParameter(EDGE_ID, strEdgeId);
  61 + try {
  62 + TenantId tenantId = getCurrentUser().getTenantId();
  63 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  64 + checkEdgeId(edgeId, Operation.READ);
  65 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  66 + return checkNotNull(edgeEventService.findEdgeEvents(tenantId, edgeId, pageLink, false));
  67 + } catch (Exception e) {
  68 + throw handleException(e);
  69 + }
  70 + }
  71 +}
... ...
... ... @@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
25 25 import org.springframework.web.bind.annotation.ResponseStatus;
26 26 import org.springframework.web.bind.annotation.RestController;
27 27 import org.thingsboard.server.common.data.audit.ActionType;
  28 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
28 29 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
29 30 import org.thingsboard.server.common.data.exception.ThingsboardException;
30 31 import org.thingsboard.server.common.data.id.EntityId;
... ... @@ -63,10 +64,13 @@ public class EntityRelationController extends BaseController {
63 64 relation.setTypeGroup(RelationTypeGroup.COMMON);
64 65 }
65 66 relationService.saveRelation(getTenantId(), relation);
  67 +
66 68 logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
67 69 ActionType.RELATION_ADD_OR_UPDATE, null, relation);
68 70 logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
69 71 ActionType.RELATION_ADD_OR_UPDATE, null, relation);
  72 +
  73 + sendRelationNotificationMsg(getTenantId(), relation, EdgeEventActionType.RELATION_ADD_OR_UPDATE);
70 74 } catch (Exception e) {
71 75 logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
72 76 ActionType.RELATION_ADD_OR_UPDATE, e, relation);
... ... @@ -104,6 +108,8 @@ public class EntityRelationController extends BaseController {
104 108 ActionType.RELATION_DELETED, null, relation);
105 109 logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
106 110 ActionType.RELATION_DELETED, null, relation);
  111 +
  112 + sendRelationNotificationMsg(getTenantId(), relation, EdgeEventActionType.RELATION_DELETED);
107 113 } catch (Exception e) {
108 114 logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
109 115 ActionType.RELATION_DELETED, e, relation);
... ...
... ... @@ -32,11 +32,20 @@ import org.springframework.web.bind.annotation.RequestParam;
32 32 import org.springframework.web.bind.annotation.ResponseBody;
33 33 import org.springframework.web.bind.annotation.ResponseStatus;
34 34 import org.springframework.web.bind.annotation.RestController;
35   -import org.thingsboard.server.common.data.*;
  35 +import org.thingsboard.server.common.data.Customer;
  36 +import org.thingsboard.server.common.data.DataConstants;
  37 +import org.thingsboard.server.common.data.EntitySubtype;
  38 +import org.thingsboard.server.common.data.EntityType;
  39 +import org.thingsboard.server.common.data.EntityView;
  40 +import org.thingsboard.server.common.data.EntityViewInfo;
  41 +import org.thingsboard.server.common.data.asset.Asset;
36 42 import org.thingsboard.server.common.data.audit.ActionType;
  43 +import org.thingsboard.server.common.data.edge.Edge;
  44 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
37 45 import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
38 46 import org.thingsboard.server.common.data.exception.ThingsboardException;
39 47 import org.thingsboard.server.common.data.id.CustomerId;
  48 +import org.thingsboard.server.common.data.id.EdgeId;
40 49 import org.thingsboard.server.common.data.id.EntityId;
41 50 import org.thingsboard.server.common.data.id.EntityViewId;
42 51 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -47,6 +56,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
47 56 import org.thingsboard.server.common.data.kv.TsKvEntry;
48 57 import org.thingsboard.server.common.data.page.PageData;
49 58 import org.thingsboard.server.common.data.page.PageLink;
  59 +import org.thingsboard.server.common.data.page.TimePageLink;
50 60 import org.thingsboard.server.dao.exception.IncorrectParameterException;
51 61 import org.thingsboard.server.dao.model.ModelConstants;
52 62 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -65,6 +75,7 @@ import java.util.stream.Collectors;
65 75
66 76 import static org.apache.commons.lang3.StringUtils.isBlank;
67 77 import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
  78 +import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
68 79
69 80 /**
70 81 * Created by Victor Basanets on 8/28/2017.
... ... @@ -150,6 +161,11 @@ public class EntityViewController extends BaseController {
150 161
151 162 logEntityAction(savedEntityView.getId(), savedEntityView, null,
152 163 entityView.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
  164 +
  165 + if (entityView.getId() != null) {
  166 + sendEntityNotificationMsg(savedEntityView.getTenantId(), savedEntityView.getId(), EdgeEventActionType.UPDATED);
  167 + }
  168 +
153 169 return savedEntityView;
154 170 } catch (Exception e) {
155 171 logEntityAction(emptyId(EntityType.ENTITY_VIEW), entityView, null,
... ... @@ -243,7 +259,7 @@ public class EntityViewController extends BaseController {
243 259 private ListenableFuture<List<Void>> copyLatestFromEntityToEntityView(EntityView entityView, SecurityUser user) {
244 260 EntityViewId entityId = entityView.getId();
245 261 List<String> keys = entityView.getKeys() != null && entityView.getKeys().getTimeseries() != null ?
246   - entityView.getKeys().getTimeseries() : Collections.emptyList();
  262 + entityView.getKeys().getTimeseries() : Collections.emptyList();
247 263 long startTs = entityView.getStartTimeMs();
248 264 long endTs = entityView.getEndTimeMs() == 0 ? Long.MAX_VALUE : entityView.getEndTimeMs();
249 265 ListenableFuture<List<String>> keysFuture;
... ... @@ -345,9 +361,14 @@ public class EntityViewController extends BaseController {
345 361 try {
346 362 EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
347 363 EntityView entityView = checkEntityViewId(entityViewId, Operation.DELETE);
  364 +
  365 + List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), entityViewId);
  366 +
348 367 entityViewService.deleteEntityView(getTenantId(), entityViewId);
349 368 logEntityAction(entityViewId, entityView, entityView.getCustomerId(),
350 369 ActionType.DELETED, null, strEntityViewId);
  370 +
  371 + sendDeleteNotificationMsg(getTenantId(), entityViewId, relatedEdgeIds);
351 372 } catch (Exception e) {
352 373 logEntityAction(emptyId(EntityType.ENTITY_VIEW),
353 374 null,
... ... @@ -388,6 +409,10 @@ public class EntityViewController extends BaseController {
388 409 logEntityAction(entityViewId, savedEntityView,
389 410 savedEntityView.getCustomerId(),
390 411 ActionType.ASSIGNED_TO_CUSTOMER, null, strEntityViewId, strCustomerId, customer.getName());
  412 +
  413 + sendEntityAssignToCustomerNotificationMsg(savedEntityView.getTenantId(), savedEntityView.getId(),
  414 + customerId, EdgeEventActionType.ASSIGNED_TO_CUSTOMER);
  415 +
391 416 return savedEntityView;
392 417 } catch (Exception e) {
393 418 logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
... ... @@ -414,6 +439,9 @@ public class EntityViewController extends BaseController {
414 439 entityView.getCustomerId(),
415 440 ActionType.UNASSIGNED_FROM_CUSTOMER, null, strEntityViewId, customer.getId().toString(), customer.getName());
416 441
  442 + sendEntityAssignToCustomerNotificationMsg(savedEntityView.getTenantId(), savedEntityView.getId(),
  443 + customer.getId(), EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER);
  444 +
417 445 return savedEntityView;
418 446 } catch (Exception e) {
419 447 logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
... ... @@ -585,4 +613,107 @@ public class EntityViewController extends BaseController {
585 613 throw handleException(e);
586 614 }
587 615 }
  616 +
  617 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  618 + @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.POST)
  619 + @ResponseBody
  620 + public EntityView assignEntityViewToEdge(@PathVariable(EDGE_ID) String strEdgeId,
  621 + @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  622 + checkParameter(EDGE_ID, strEdgeId);
  623 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  624 + try {
  625 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  626 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  627 +
  628 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  629 + checkEntityViewId(entityViewId, Operation.ASSIGN_TO_EDGE);
  630 +
  631 + EntityView savedEntityView = checkNotNull(entityViewService.assignEntityViewToEdge(getTenantId(), entityViewId, edgeId));
  632 + logEntityAction(entityViewId, savedEntityView,
  633 + savedEntityView.getCustomerId(),
  634 + ActionType.ASSIGNED_TO_EDGE, null, strEntityViewId, strEdgeId, edge.getName());
  635 +
  636 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedEntityView.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE);
  637 +
  638 + return savedEntityView;
  639 + } catch (Exception e) {
  640 + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
  641 + null,
  642 + ActionType.ASSIGNED_TO_EDGE, e, strEntityViewId, strEdgeId);
  643 + throw handleException(e);
  644 + }
  645 + }
  646 +
  647 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  648 + @RequestMapping(value = "/edge/{edgeId}/entityView/{entityViewId}", method = RequestMethod.DELETE)
  649 + @ResponseBody
  650 + public EntityView unassignEntityViewFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
  651 + @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
  652 + checkParameter(EDGE_ID, strEdgeId);
  653 + checkParameter(ENTITY_VIEW_ID, strEntityViewId);
  654 + try {
  655 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  656 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  657 +
  658 + EntityViewId entityViewId = new EntityViewId(toUUID(strEntityViewId));
  659 + EntityView entityView = checkEntityViewId(entityViewId, Operation.UNASSIGN_FROM_EDGE);
  660 +
  661 + EntityView savedEntityView = checkNotNull(entityViewService.unassignEntityViewFromEdge(getTenantId(), entityViewId, edgeId));
  662 + logEntityAction(entityViewId, entityView,
  663 + entityView.getCustomerId(),
  664 + ActionType.UNASSIGNED_FROM_EDGE, null, strEntityViewId, strEdgeId, edge.getName());
  665 +
  666 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedEntityView.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE);
  667 +
  668 + return savedEntityView;
  669 + } catch (Exception e) {
  670 + logEntityAction(emptyId(EntityType.ENTITY_VIEW), null,
  671 + null,
  672 + ActionType.UNASSIGNED_FROM_EDGE, e, strEntityViewId, strEdgeId);
  673 + throw handleException(e);
  674 + }
  675 + }
  676 +
  677 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  678 + @RequestMapping(value = "/edge/{edgeId}/entityViews", params = {"pageSize", "page"}, method = RequestMethod.GET)
  679 + @ResponseBody
  680 + public PageData<EntityView> getEdgeEntityViews(
  681 + @PathVariable(EDGE_ID) String strEdgeId,
  682 + @RequestParam int pageSize,
  683 + @RequestParam int page,
  684 + @RequestParam(required = false) String type,
  685 + @RequestParam(required = false) String textSearch,
  686 + @RequestParam(required = false) String sortProperty,
  687 + @RequestParam(required = false) String sortOrder,
  688 + @RequestParam(required = false) Long startTime,
  689 + @RequestParam(required = false) Long endTime) throws ThingsboardException {
  690 + checkParameter(EDGE_ID, strEdgeId);
  691 + try {
  692 + TenantId tenantId = getCurrentUser().getTenantId();
  693 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  694 + checkEdgeId(edgeId, Operation.READ);
  695 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
  696 + PageData<EntityView> nonFilteredResult;
  697 + if (type != null && type.trim().length() > 0) {
  698 + nonFilteredResult = entityViewService.findEntityViewsByTenantIdAndEdgeIdAndType(tenantId, edgeId, type, pageLink);
  699 + } else {
  700 + nonFilteredResult = entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
  701 + }
  702 + List<EntityView> filteredEntityViews = nonFilteredResult.getData().stream().filter(entityView -> {
  703 + try {
  704 + accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, Operation.READ, entityView.getId(), entityView);
  705 + return true;
  706 + } catch (ThingsboardException e) {
  707 + return false;
  708 + }
  709 + }).collect(Collectors.toList());
  710 + PageData<EntityView> filteredResult = new PageData<>(filteredEntityViews,
  711 + nonFilteredResult.getTotalPages(),
  712 + nonFilteredResult.getTotalElements(),
  713 + nonFilteredResult.hasNext());
  714 + return checkNotNull(filteredResult);
  715 + } catch (Exception e) {
  716 + throw handleException(e);
  717 + }
  718 + }
588 719 }
... ...
... ... @@ -42,7 +42,10 @@ import org.thingsboard.server.common.data.DataConstants;
42 42 import org.thingsboard.server.common.data.EntityType;
43 43 import org.thingsboard.server.common.data.Event;
44 44 import org.thingsboard.server.common.data.audit.ActionType;
  45 +import org.thingsboard.server.common.data.edge.Edge;
  46 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
45 47 import org.thingsboard.server.common.data.exception.ThingsboardException;
  48 +import org.thingsboard.server.common.data.id.EdgeId;
46 49 import org.thingsboard.server.common.data.id.RuleChainId;
47 50 import org.thingsboard.server.common.data.id.RuleNodeId;
48 51 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -54,6 +57,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
54 57 import org.thingsboard.server.common.data.rule.RuleChainData;
55 58 import org.thingsboard.server.common.data.rule.RuleChainImportResult;
56 59 import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  60 +import org.thingsboard.server.common.data.rule.RuleChainType;
57 61 import org.thingsboard.server.common.data.rule.RuleNode;
58 62 import org.thingsboard.server.common.msg.TbMsg;
59 63 import org.thingsboard.server.common.msg.TbMsgDataType;
... ... @@ -138,13 +142,21 @@ public class RuleChainController extends BaseController {
138 142
139 143 RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));
140 144
141   - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(),
142   - created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  145 + if (RuleChainType.CORE.equals(savedRuleChain.getType())) {
  146 + tbClusterService.onEntityStateChange(ruleChain.getTenantId(), savedRuleChain.getId(),
  147 + created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED);
  148 + }
143 149
144 150 logEntityAction(savedRuleChain.getId(), savedRuleChain,
145 151 null,
146 152 created ? ActionType.ADDED : ActionType.UPDATED, null);
147 153
  154 + if (RuleChainType.EDGE.equals(savedRuleChain.getType())) {
  155 + if (!created) {
  156 + sendEntityNotificationMsg(savedRuleChain.getTenantId(), savedRuleChain.getId(), EdgeEventActionType.UPDATED);
  157 + }
  158 + }
  159 +
148 160 return savedRuleChain;
149 161 } catch (Exception e) {
150 162
... ... @@ -234,12 +246,19 @@ public class RuleChainController extends BaseController {
234 246 RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE);
235 247 RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData));
236 248
237   - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
  249 + if (RuleChainType.CORE.equals(ruleChain.getType())) {
  250 + tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED);
  251 + }
238 252
239 253 logEntityAction(ruleChain.getId(), ruleChain,
240 254 null,
241 255 ActionType.UPDATED, null, ruleChainMetaData);
242 256
  257 + if (RuleChainType.EDGE.equals(ruleChain.getType())) {
  258 + sendEntityNotificationMsg(ruleChain.getTenantId(),
  259 + ruleChain.getId(), EdgeEventActionType.UPDATED);
  260 + }
  261 +
243 262 return savedRuleChainMetaData;
244 263 } catch (Exception e) {
245 264
... ... @@ -256,13 +275,18 @@ public class RuleChainController extends BaseController {
256 275 public PageData<RuleChain> getRuleChains(
257 276 @RequestParam int pageSize,
258 277 @RequestParam int page,
  278 + @RequestParam(value = "type", required = false) String typeStr,
259 279 @RequestParam(required = false) String textSearch,
260 280 @RequestParam(required = false) String sortProperty,
261 281 @RequestParam(required = false) String sortOrder) throws ThingsboardException {
262 282 try {
263 283 TenantId tenantId = getCurrentUser().getTenantId();
264 284 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
265   - return checkNotNull(ruleChainService.findTenantRuleChains(tenantId, pageLink));
  285 + RuleChainType type = RuleChainType.CORE;
  286 + if (typeStr != null && typeStr.trim().length() > 0) {
  287 + type = RuleChainType.valueOf(typeStr);
  288 + }
  289 + return checkNotNull(ruleChainService.findTenantRuleChainsByType(tenantId, type, pageLink));
266 290 } catch (Exception e) {
267 291 throw handleException(e);
268 292 }
... ... @@ -281,19 +305,30 @@ public class RuleChainController extends BaseController {
281 305
282 306 Set<RuleChainId> referencingRuleChainIds = referencingRuleNodes.stream().map(RuleNode::getRuleChainId).collect(Collectors.toSet());
283 307
  308 + List<EdgeId> relatedEdgeIds = null;
  309 + if (RuleChainType.EDGE.equals(ruleChain.getType())) {
  310 + relatedEdgeIds = findRelatedEdgeIds(getTenantId(), ruleChainId);
  311 + }
  312 +
284 313 ruleChainService.deleteRuleChainById(getTenantId(), ruleChainId);
285 314
286 315 referencingRuleChainIds.remove(ruleChain.getId());
287 316
288   - referencingRuleChainIds.forEach(referencingRuleChainId ->
289   - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED));
  317 + if (RuleChainType.CORE.equals(ruleChain.getType())) {
  318 + referencingRuleChainIds.forEach(referencingRuleChainId ->
  319 + tbClusterService.onEntityStateChange(ruleChain.getTenantId(), referencingRuleChainId, ComponentLifecycleEvent.UPDATED));
290 320
291   - tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
  321 + tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.DELETED);
  322 + }
292 323
293 324 logEntityAction(ruleChainId, ruleChain,
294 325 null,
295 326 ActionType.DELETED, null, strRuleChainId);
296 327
  328 + if (RuleChainType.EDGE.equals(ruleChain.getType())) {
  329 + sendDeleteNotificationMsg(ruleChain.getTenantId(), ruleChain.getId(), relatedEdgeIds);
  330 + }
  331 +
297 332 } catch (Exception e) {
298 333 logEntityAction(emptyId(EntityType.RULE_CHAIN),
299 334 null,
... ... @@ -411,7 +446,7 @@ public class RuleChainController extends BaseController {
411 446 public void importRuleChains(@RequestBody RuleChainData ruleChainData, @RequestParam(required = false, defaultValue = "false") boolean overwrite) throws ThingsboardException {
412 447 try {
413 448 TenantId tenantId = getCurrentUser().getTenantId();
414   - List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, overwrite);
  449 + List<RuleChainImportResult> importResults = ruleChainService.importTenantRuleChains(tenantId, ruleChainData, RuleChainType.CORE, overwrite);
415 450 if (!CollectionUtils.isEmpty(importResults)) {
416 451 for (RuleChainImportResult importResult : importResults) {
417 452 tbClusterService.onEntityStateChange(importResult.getTenantId(), importResult.getRuleChainId(), importResult.getLifecycleEvent());
... ... @@ -452,5 +487,160 @@ public class RuleChainController extends BaseController {
452 487 return msgData;
453 488 }
454 489
  490 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  491 + @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.POST)
  492 + @ResponseBody
  493 + public RuleChain assignRuleChainToEdge(@PathVariable("edgeId") String strEdgeId,
  494 + @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
  495 + checkParameter("edgeId", strEdgeId);
  496 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  497 + try {
  498 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  499 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  500 +
  501 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  502 + checkRuleChain(ruleChainId, Operation.ASSIGN_TO_EDGE);
  503 +
  504 + RuleChain savedRuleChain = checkNotNull(ruleChainService.assignRuleChainToEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId));
  505 +
  506 + logEntityAction(ruleChainId, savedRuleChain,
  507 + null,
  508 + ActionType.ASSIGNED_TO_EDGE, null, strRuleChainId, strEdgeId, edge.getName());
  509 +
  510 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedRuleChain.getId(), EdgeEventActionType.ASSIGNED_TO_EDGE);
  511 +
  512 + return savedRuleChain;
  513 + } catch (Exception e) {
  514 +
  515 + logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
  516 + null,
  517 + ActionType.ASSIGNED_TO_EDGE, e, strRuleChainId, strEdgeId);
  518 +
  519 + throw handleException(e);
  520 + }
  521 + }
  522 +
  523 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  524 + @RequestMapping(value = "/edge/{edgeId}/ruleChain/{ruleChainId}", method = RequestMethod.DELETE)
  525 + @ResponseBody
  526 + public RuleChain unassignRuleChainFromEdge(@PathVariable("edgeId") String strEdgeId,
  527 + @PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
  528 + checkParameter("edgeId", strEdgeId);
  529 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  530 + try {
  531 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  532 + Edge edge = checkEdgeId(edgeId, Operation.READ);
  533 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  534 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.UNASSIGN_FROM_EDGE);
  535 +
  536 + RuleChain savedRuleChain = checkNotNull(ruleChainService.unassignRuleChainFromEdge(getCurrentUser().getTenantId(), ruleChainId, edgeId, false));
  537 +
  538 + logEntityAction(ruleChainId, ruleChain,
  539 + null,
  540 + ActionType.UNASSIGNED_FROM_EDGE, null, strRuleChainId, strEdgeId, edge.getName());
  541 +
  542 + sendEntityAssignToEdgeNotificationMsg(getTenantId(), edgeId, savedRuleChain.getId(), EdgeEventActionType.UNASSIGNED_FROM_EDGE);
  543 +
  544 + return savedRuleChain;
  545 + } catch (Exception e) {
  546 +
  547 + logEntityAction(emptyId(EntityType.RULE_CHAIN), null,
  548 + null,
  549 + ActionType.UNASSIGNED_FROM_EDGE, e, strRuleChainId, strEdgeId);
  550 +
  551 + throw handleException(e);
  552 + }
  553 + }
  554 +
  555 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  556 + @RequestMapping(value = "/edge/{edgeId}/ruleChains", params = {"pageSize", "page"}, method = RequestMethod.GET)
  557 + @ResponseBody
  558 + public PageData<RuleChain> getEdgeRuleChains(
  559 + @PathVariable("edgeId") String strEdgeId,
  560 + @RequestParam int pageSize,
  561 + @RequestParam int page,
  562 + @RequestParam(required = false) String textSearch,
  563 + @RequestParam(required = false) String sortProperty,
  564 + @RequestParam(required = false) String sortOrder) throws ThingsboardException {
  565 + checkParameter("edgeId", strEdgeId);
  566 + try {
  567 + TenantId tenantId = getCurrentUser().getTenantId();
  568 + EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
  569 + checkEdgeId(edgeId, Operation.READ);
  570 + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
  571 + return checkNotNull(ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink));
  572 + } catch (Exception e) {
  573 + throw handleException(e);
  574 + }
  575 + }
  576 +
  577 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  578 + @RequestMapping(value = "/ruleChain/{ruleChainId}/edgeTemplateRoot", method = RequestMethod.POST)
  579 + @ResponseBody
  580 + public RuleChain setEdgeTemplateRootRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
  581 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  582 + try {
  583 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  584 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.WRITE);
  585 + ruleChainService.setEdgeTemplateRootRuleChain(getTenantId(), ruleChainId);
  586 + return ruleChain;
  587 + } catch (Exception e) {
  588 + logEntityAction(emptyId(EntityType.RULE_CHAIN),
  589 + null,
  590 + null,
  591 + ActionType.UPDATED, e, strRuleChainId);
  592 + throw handleException(e);
  593 + }
  594 + }
  595 +
  596 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  597 + @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.POST)
  598 + @ResponseBody
  599 + public RuleChain setAutoAssignToEdgeRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
  600 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  601 + try {
  602 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  603 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.WRITE);
  604 + ruleChainService.setAutoAssignToEdgeRuleChain(getTenantId(), ruleChainId);
  605 + return ruleChain;
  606 + } catch (Exception e) {
  607 + logEntityAction(emptyId(EntityType.RULE_CHAIN),
  608 + null,
  609 + null,
  610 + ActionType.UPDATED, e, strRuleChainId);
  611 + throw handleException(e);
  612 + }
  613 + }
  614 +
  615 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  616 + @RequestMapping(value = "/ruleChain/{ruleChainId}/autoAssignToEdge", method = RequestMethod.DELETE)
  617 + @ResponseBody
  618 + public RuleChain unsetAutoAssignToEdgeRuleChain(@PathVariable(RULE_CHAIN_ID) String strRuleChainId) throws ThingsboardException {
  619 + checkParameter(RULE_CHAIN_ID, strRuleChainId);
  620 + try {
  621 + RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
  622 + RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.WRITE);
  623 + ruleChainService.unsetAutoAssignToEdgeRuleChain(getTenantId(), ruleChainId);
  624 + return ruleChain;
  625 + } catch (Exception e) {
  626 + logEntityAction(emptyId(EntityType.RULE_CHAIN),
  627 + null,
  628 + null,
  629 + ActionType.UPDATED, e, strRuleChainId);
  630 + throw handleException(e);
  631 + }
  632 + }
  633 +
  634 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
  635 + @RequestMapping(value = "/ruleChain/autoAssignToEdgeRuleChains", method = RequestMethod.GET)
  636 + @ResponseBody
  637 + public List<RuleChain> getAutoAssignToEdgeRuleChains() throws ThingsboardException {
  638 + try {
  639 + TenantId tenantId = getCurrentUser().getTenantId();
  640 + return checkNotNull(ruleChainService.findAutoAssignToEdgeRuleChainsByTenantId(tenantId)).get();
  641 + } catch (Exception e) {
  642 + throw handleException(e);
  643 + }
  644 + }
455 645
456 646 }
... ...
... ... @@ -95,6 +95,9 @@ public class TenantController extends BaseController {
95 95 tenant = checkNotNull(tenantService.saveTenant(tenant));
96 96 if (newTenant) {
97 97 installScripts.createDefaultRuleChains(tenant.getId());
  98 + if (edgesEnabled) {
  99 + installScripts.createDefaultEdgeRuleChains(tenant.getId());
  100 + }
98 101 }
99 102 tenantProfileCache.evict(tenant.getId());
100 103 tbClusterService.onTenantChange(tenant, null);
... ...
... ... @@ -36,9 +36,11 @@ import org.thingsboard.rule.engine.api.MailService;
36 36 import org.thingsboard.server.common.data.EntityType;
37 37 import org.thingsboard.server.common.data.User;
38 38 import org.thingsboard.server.common.data.audit.ActionType;
  39 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
39 40 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
40 41 import org.thingsboard.server.common.data.exception.ThingsboardException;
41 42 import org.thingsboard.server.common.data.id.CustomerId;
  43 +import org.thingsboard.server.common.data.id.EdgeId;
42 44 import org.thingsboard.server.common.data.id.TenantId;
43 45 import org.thingsboard.server.common.data.id.UserId;
44 46 import org.thingsboard.server.common.data.page.PageData;
... ... @@ -57,6 +59,7 @@ import org.thingsboard.server.service.security.permission.Resource;
57 59 import org.thingsboard.server.service.security.system.SystemSecurityService;
58 60
59 61 import javax.servlet.http.HttpServletRequest;
  62 +import java.util.List;
60 63
61 64 @RequiredArgsConstructor
62 65 @RestController
... ... @@ -178,6 +181,9 @@ public class UserController extends BaseController {
178 181 savedUser.getCustomerId(),
179 182 user.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
180 183
  184 + sendEntityNotificationMsg(getTenantId(), savedUser.getId(),
  185 + user.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
  186 +
181 187 return savedUser;
182 188 } catch (Exception e) {
183 189
... ... @@ -247,12 +253,17 @@ public class UserController extends BaseController {
247 253 try {
248 254 UserId userId = new UserId(toUUID(strUserId));
249 255 User user = checkUserId(userId, Operation.DELETE);
  256 +
  257 + List<EdgeId> relatedEdgeIds = findRelatedEdgeIds(getTenantId(), userId);
  258 +
250 259 userService.deleteUser(getCurrentUser().getTenantId(), userId);
251 260
252 261 logEntityAction(userId, user,
253 262 user.getCustomerId(),
254 263 ActionType.DELETED, null, strUserId);
255 264
  265 + sendDeleteNotificationMsg(getTenantId(), userId, relatedEdgeIds);
  266 +
256 267 } catch (Exception e) {
257 268 logEntityAction(emptyId(EntityType.USER),
258 269 null,
... ...
... ... @@ -15,6 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import lombok.extern.slf4j.Slf4j;
18 19 import org.springframework.http.HttpStatus;
19 20 import org.springframework.security.access.prepost.PreAuthorize;
20 21 import org.springframework.web.bind.annotation.PathVariable;
... ... @@ -25,6 +26,8 @@ import org.springframework.web.bind.annotation.RequestParam;
25 26 import org.springframework.web.bind.annotation.ResponseBody;
26 27 import org.springframework.web.bind.annotation.ResponseStatus;
27 28 import org.springframework.web.bind.annotation.RestController;
  29 +import org.thingsboard.server.common.data.EntityType;
  30 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
28 31 import org.thingsboard.server.common.data.exception.ThingsboardException;
29 32 import org.thingsboard.server.common.data.id.TenantId;
30 33 import org.thingsboard.server.common.data.id.WidgetTypeId;
... ... @@ -39,6 +42,7 @@ import org.thingsboard.server.service.security.permission.Resource;
39 42
40 43 import java.util.List;
41 44
  45 +@Slf4j
42 46 @RestController
43 47 @TbCoreComponent
44 48 @RequestMapping("/api")
... ... @@ -69,8 +73,12 @@ public class WidgetTypeController extends BaseController {
69 73 }
70 74
71 75 checkEntity(widgetTypeDetails.getId(), widgetTypeDetails, Resource.WIDGET_TYPE);
  76 + WidgetTypeDetails savedWidgetTypeDetails = widgetTypeService.saveWidgetType(widgetTypeDetails);
72 77
73   - return checkNotNull(widgetTypeService.saveWidgetType(widgetTypeDetails));
  78 + sendEntityNotificationMsg(getTenantId(), savedWidgetTypeDetails.getId(),
  79 + widgetTypeDetails.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
  80 +
  81 + return checkNotNull(savedWidgetTypeDetails);
74 82 } catch (Exception e) {
75 83 throw handleException(e);
76 84 }
... ... @@ -85,6 +93,9 @@ public class WidgetTypeController extends BaseController {
85 93 WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
86 94 checkWidgetTypeId(widgetTypeId, Operation.DELETE);
87 95 widgetTypeService.deleteWidgetType(getCurrentUser().getTenantId(), widgetTypeId);
  96 +
  97 + sendEntityNotificationMsg(getTenantId(), widgetTypeId, EdgeEventActionType.DELETED);
  98 +
88 99 } catch (Exception e) {
89 100 throw handleException(e);
90 101 }
... ...
... ... @@ -25,6 +25,7 @@ import org.springframework.web.bind.annotation.RequestParam;
25 25 import org.springframework.web.bind.annotation.ResponseBody;
26 26 import org.springframework.web.bind.annotation.ResponseStatus;
27 27 import org.springframework.web.bind.annotation.RestController;
  28 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
28 29 import org.thingsboard.server.common.data.exception.ThingsboardException;
29 30 import org.thingsboard.server.common.data.id.TenantId;
30 31 import org.thingsboard.server.common.data.id.WidgetsBundleId;
... ... @@ -68,7 +69,12 @@ public class WidgetsBundleController extends BaseController {
68 69 }
69 70
70 71 checkEntity(widgetsBundle.getId(), widgetsBundle, Resource.WIDGETS_BUNDLE);
71   - return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
  72 + WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
  73 +
  74 + sendEntityNotificationMsg(getTenantId(), savedWidgetsBundle.getId(),
  75 + widgetsBundle.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED);
  76 +
  77 + return checkNotNull(savedWidgetsBundle);
72 78 } catch (Exception e) {
73 79 throw handleException(e);
74 80 }
... ... @@ -83,6 +89,9 @@ public class WidgetsBundleController extends BaseController {
83 89 WidgetsBundleId widgetsBundleId = new WidgetsBundleId(toUUID(strWidgetsBundleId));
84 90 checkWidgetsBundleId(widgetsBundleId, Operation.DELETE);
85 91 widgetsBundleService.deleteWidgetsBundle(getTenantId(), widgetsBundleId);
  92 +
  93 + sendEntityNotificationMsg(getTenantId(), widgetsBundleId, EdgeEventActionType.DELETED);
  94 +
86 95 } catch (Exception e) {
87 96 throw handleException(e);
88 97 }
... ...
... ... @@ -27,6 +27,7 @@ import org.springframework.security.authentication.LockedException;
27 27 import org.springframework.security.core.AuthenticationException;
28 28 import org.springframework.security.web.access.AccessDeniedHandler;
29 29 import org.springframework.stereotype.Component;
  30 +import org.springframework.web.client.HttpClientErrorException;
30 31 import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
31 32 import org.thingsboard.server.common.data.exception.ThingsboardException;
32 33 import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
... ... @@ -66,7 +67,12 @@ public class ThingsboardErrorResponseHandler implements AccessDeniedHandler {
66 67 response.setContentType(MediaType.APPLICATION_JSON_VALUE);
67 68
68 69 if (exception instanceof ThingsboardException) {
69   - handleThingsboardException((ThingsboardException) exception, response);
  70 + ThingsboardException thingsboardException = (ThingsboardException) exception;
  71 + if (thingsboardException.getErrorCode() == ThingsboardErrorCode.SUBSCRIPTION_VIOLATION) {
  72 + handleSubscriptionException((ThingsboardException) exception, response);
  73 + } else {
  74 + handleThingsboardException((ThingsboardException) exception, response);
  75 + }
70 76 } else if (exception instanceof TbRateLimitsException) {
71 77 handleRateLimitException(response, (TbRateLimitsException) exception);
72 78 } else if (exception instanceof AccessDeniedException) {
... ... @@ -126,6 +132,11 @@ public class ThingsboardErrorResponseHandler implements AccessDeniedHandler {
126 132 ThingsboardErrorCode.TOO_MANY_REQUESTS, HttpStatus.TOO_MANY_REQUESTS));
127 133 }
128 134
  135 + private void handleSubscriptionException(ThingsboardException subscriptionException, HttpServletResponse response) throws IOException {
  136 + response.setStatus(HttpStatus.FORBIDDEN.value());
  137 + mapper.writeValue(response.getWriter(),
  138 + (new ObjectMapper()).readValue(((HttpClientErrorException) subscriptionException.getCause()).getResponseBodyAsByteArray(), Object.class));
  139 + }
129 140
130 141 private void handleAccessDeniedException(HttpServletResponse response) throws IOException {
131 142 response.setStatus(HttpStatus.FORBIDDEN.value());
... ...
... ... @@ -193,7 +193,9 @@ public class ThingsboardInstallService {
193 193 databaseEntitiesUpgradeService.upgradeDatabase("3.2.1");
194 194 case "3.2.2":
195 195 log.info("Upgrading ThingsBoard from version 3.2.2 to 3.3.0 ...");
196   - databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
  196 + databaseEntitiesUpgradeService.upgradeDatabase("3.2.2");
  197 +
  198 + dataUpdateService.updateData("3.2.2");
197 199
198 200 log.info("Updating system data...");
199 201 systemDataLoaderService.updateSystemWidgets();
... ...
... ... @@ -34,13 +34,13 @@ import org.thingsboard.rule.engine.api.TbRelationTypes;
34 34 import org.thingsboard.server.common.data.id.TenantId;
35 35 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
36 36 import org.thingsboard.server.common.data.plugin.ComponentType;
  37 +import org.thingsboard.server.common.data.rule.RuleChainType;
37 38 import org.thingsboard.server.dao.component.ComponentDescriptorService;
38 39
39 40 import javax.annotation.PostConstruct;
40 41 import java.lang.annotation.Annotation;
41 42 import java.util.ArrayList;
42 43 import java.util.Arrays;
43   -import java.util.Collection;
44 44 import java.util.Collections;
45 45 import java.util.HashMap;
46 46 import java.util.HashSet;
... ... @@ -65,7 +65,9 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
65 65
66 66 private Map<String, ComponentDescriptor> components = new HashMap<>();
67 67
68   - private Map<ComponentType, List<ComponentDescriptor>> componentsMap = new HashMap<>();
  68 + private Map<ComponentType, List<ComponentDescriptor>> coreComponentsMap = new HashMap<>();
  69 +
  70 + private Map<ComponentType, List<ComponentDescriptor>> edgeComponentsMap = new HashMap<>();
69 71
70 72 private ObjectMapper mapper = new ObjectMapper();
71 73
... ... @@ -93,7 +95,7 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
93 95 ComponentType type = ruleNodeAnnotation.type();
94 96 ComponentDescriptor component = scanAndPersistComponent(def, type);
95 97 components.put(component.getClazz(), component);
96   - componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
  98 + putComponentIntoMaps(type, ruleNodeAnnotation, component);
97 99 break;
98 100 } catch (Exception e) {
99 101 log.trace("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e);
... ... @@ -113,22 +115,35 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
113 115 }
114 116 }
115 117
116   - private void registerComponents(ComponentType type, Class<? extends Annotation> annotation) {
117   - List<ComponentDescriptor> components = persist(getBeanDefinitions(annotation), type);
118   - componentsMap.put(type, components);
119   - registerComponents(components);
120   - }
121   -
122   - private void registerComponents(Collection<ComponentDescriptor> comps) {
123   - comps.forEach(c -> components.put(c.getClazz(), c));
  118 + private void putComponentIntoMaps(ComponentType type, RuleNode ruleNodeAnnotation, ComponentDescriptor component) {
  119 + boolean ruleChainTypesMethodAvailable;
  120 + try {
  121 + ruleNodeAnnotation.getClass().getMethod("ruleChainTypes");
  122 + ruleChainTypesMethodAvailable = true;
  123 + } catch (NoSuchMethodException exception) {
  124 + log.warn("[{}] does not have ruleChainTypes. Probably extension class compiled before 3.3 release. " +
  125 + "Please update your extensions and compile using latest 3.3 release dependency", ruleNodeAnnotation.name());
  126 + ruleChainTypesMethodAvailable = false;
  127 + }
  128 + if (ruleChainTypesMethodAvailable) {
  129 + if (ruleChainTypeContainsArray(RuleChainType.CORE, ruleNodeAnnotation.ruleChainTypes())) {
  130 + coreComponentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
  131 + }
  132 + if (ruleChainTypeContainsArray(RuleChainType.EDGE, ruleNodeAnnotation.ruleChainTypes())) {
  133 + edgeComponentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
  134 + }
  135 + } else {
  136 + coreComponentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
  137 + }
124 138 }
125 139
126   - private List<ComponentDescriptor> persist(Set<BeanDefinition> filterDefs, ComponentType type) {
127   - List<ComponentDescriptor> result = new ArrayList<>();
128   - for (BeanDefinition def : filterDefs) {
129   - result.add(scanAndPersistComponent(def, type));
  140 + private boolean ruleChainTypeContainsArray(RuleChainType ruleChainType, RuleChainType[] array) {
  141 + for (RuleChainType tmp : array) {
  142 + if (ruleChainType.equals(tmp)) {
  143 + return true;
  144 + }
130 145 }
131   - return result;
  146 + return false;
132 147 }
133 148
134 149 private ComponentDescriptor scanAndPersistComponent(BeanDefinition def, ComponentType type) {
... ... @@ -222,25 +237,47 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
222 237 }
223 238
224 239 @Override
225   - public List<ComponentDescriptor> getComponents(ComponentType type) {
226   - if (componentsMap.containsKey(type)) {
227   - return Collections.unmodifiableList(componentsMap.get(type));
  240 + public List<ComponentDescriptor> getComponents(ComponentType type, RuleChainType ruleChainType) {
  241 + if (RuleChainType.CORE.equals(ruleChainType)) {
  242 + if (coreComponentsMap.containsKey(type)) {
  243 + return Collections.unmodifiableList(coreComponentsMap.get(type));
  244 + } else {
  245 + return Collections.emptyList();
  246 + }
  247 + } else if (RuleChainType.EDGE.equals(ruleChainType)) {
  248 + if (edgeComponentsMap.containsKey(type)) {
  249 + return Collections.unmodifiableList(edgeComponentsMap.get(type));
  250 + } else {
  251 + return Collections.emptyList();
  252 + }
228 253 } else {
229   - return Collections.emptyList();
  254 + log.error("Unsupported rule chain type {}", ruleChainType);
  255 + throw new RuntimeException("Unsupported rule chain type " + ruleChainType);
230 256 }
231 257 }
232 258
233 259 @Override
234   - public List<ComponentDescriptor> getComponents(Set<ComponentType> types) {
235   - List<ComponentDescriptor> result = new ArrayList<>();
236   - types.stream().filter(type -> componentsMap.containsKey(type)).forEach(type -> {
237   - result.addAll(componentsMap.get(type));
238   - });
239   - return Collections.unmodifiableList(result);
  260 + public List<ComponentDescriptor> getComponents(Set<ComponentType> types, RuleChainType ruleChainType) {
  261 + if (RuleChainType.CORE.equals(ruleChainType)) {
  262 + return getComponents(types, coreComponentsMap);
  263 + } else if (RuleChainType.EDGE.equals(ruleChainType)) {
  264 + return getComponents(types, edgeComponentsMap);
  265 + } else {
  266 + log.error("Unsupported rule chain type {}", ruleChainType);
  267 + throw new RuntimeException("Unsupported rule chain type " + ruleChainType);
  268 + }
240 269 }
241 270
242 271 @Override
243 272 public Optional<ComponentDescriptor> getComponent(String clazz) {
244 273 return Optional.ofNullable(components.get(clazz));
245 274 }
  275 +
  276 + private List<ComponentDescriptor> getComponents(Set<ComponentType> types, Map<ComponentType, List<ComponentDescriptor>> componentsMap) {
  277 + List<ComponentDescriptor> result = new ArrayList<>();
  278 + types.stream().filter(componentsMap::containsKey).forEach(type -> {
  279 + result.addAll(componentsMap.get(type));
  280 + });
  281 + return Collections.unmodifiableList(result);
  282 + }
246 283 }
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.service.component;
17 17
18 18 import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
19 19 import org.thingsboard.server.common.data.plugin.ComponentType;
  20 +import org.thingsboard.server.common.data.rule.RuleChainType;
20 21
21 22 import java.util.List;
22 23 import java.util.Optional;
... ... @@ -29,10 +30,9 @@ public interface ComponentDiscoveryService {
29 30
30 31 void discoverComponents();
31 32
32   - List<ComponentDescriptor> getComponents(ComponentType type);
  33 + List<ComponentDescriptor> getComponents(ComponentType type, RuleChainType ruleChainType);
33 34
34   - List<ComponentDescriptor> getComponents(Set<ComponentType> types);
  35 + List<ComponentDescriptor> getComponents(Set<ComponentType> types, RuleChainType ruleChainType);
35 36
36 37 Optional<ComponentDescriptor> getComponent(String clazz);
37   -
38 38 }
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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 com.fasterxml.jackson.core.JsonProcessingException;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import com.google.common.util.concurrent.FutureCallback;
  22 +import com.google.common.util.concurrent.Futures;
  23 +import com.google.common.util.concurrent.ListenableFuture;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.checkerframework.checker.nullness.qual.Nullable;
  26 +import org.springframework.beans.factory.annotation.Autowired;
  27 +import org.springframework.stereotype.Service;
  28 +import org.thingsboard.server.common.data.EdgeUtils;
  29 +import org.thingsboard.server.common.data.EntityType;
  30 +import org.thingsboard.server.common.data.User;
  31 +import org.thingsboard.server.common.data.alarm.Alarm;
  32 +import org.thingsboard.server.common.data.edge.Edge;
  33 +import org.thingsboard.server.common.data.edge.EdgeEvent;
  34 +import org.thingsboard.server.common.data.edge.EdgeEventActionType;
  35 +import org.thingsboard.server.common.data.edge.EdgeEventType;
  36 +import org.thingsboard.server.common.data.id.AlarmId;
  37 +import org.thingsboard.server.common.data.id.CustomerId;
  38 +import org.thingsboard.server.common.data.id.EdgeId;
  39 +import org.thingsboard.server.common.data.id.EntityId;
  40 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  41 +import org.thingsboard.server.common.data.id.RuleChainId;
  42 +import org.thingsboard.server.common.data.id.TenantId;
  43 +import org.thingsboard.server.common.data.page.PageData;
  44 +import org.thingsboard.server.common.data.page.PageLink;
  45 +import org.thingsboard.server.common.data.page.TimePageLink;
  46 +import org.thingsboard.server.common.data.relation.EntityRelation;
  47 +import org.thingsboard.server.common.data.rule.RuleChain;
  48 +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo;
  49 +import org.thingsboard.server.common.msg.queue.TbCallback;
  50 +import org.thingsboard.server.dao.alarm.AlarmService;
  51 +import org.thingsboard.server.dao.edge.EdgeEventService;
  52 +import org.thingsboard.server.dao.edge.EdgeService;
  53 +import org.thingsboard.server.dao.rule.RuleChainService;
  54 +import org.thingsboard.server.dao.user.UserService;
  55 +import org.thingsboard.server.gen.transport.TransportProtos;
  56 +import org.thingsboard.server.queue.util.TbCoreComponent;
  57 +import org.thingsboard.server.service.executors.DbCallbackExecutorService;
  58 +import org.thingsboard.server.service.queue.TbClusterService;
  59 +
  60 +import javax.annotation.PostConstruct;
  61 +import javax.annotation.PreDestroy;
  62 +import java.io.IOException;
  63 +import java.util.ArrayList;
  64 +import java.util.HashSet;
  65 +import java.util.List;
  66 +import java.util.Set;
  67 +import java.util.UUID;
  68 +import java.util.concurrent.ExecutorService;
  69 +import java.util.concurrent.Executors;
  70 +
  71 +@Service
  72 +@TbCoreComponent
  73 +@Slf4j
  74 +public class DefaultEdgeNotificationService implements EdgeNotificationService {
  75 +
  76 + private static final ObjectMapper mapper = new ObjectMapper();
  77 +
  78 + private static final int DEFAULT_LIMIT = 100;
  79 +
  80 + @Autowired
  81 + private EdgeService edgeService;
  82 +
  83 + @Autowired
  84 + private AlarmService alarmService;
  85 +
  86 + @Autowired
  87 + private UserService userService;
  88 +
  89 + @Autowired
  90 + private RuleChainService ruleChainService;
  91 +
  92 + @Autowired
  93 + private EdgeEventService edgeEventService;
  94 +
  95 + @Autowired
  96 + private TbClusterService clusterService;
  97 +
  98 + @Autowired
  99 + private DbCallbackExecutorService dbCallbackExecutorService;
  100 +
  101 + private ExecutorService tsCallBackExecutor;
  102 +
  103 + @PostConstruct
  104 + public void initExecutor() {
  105 + tsCallBackExecutor = Executors.newSingleThreadExecutor();
  106 + }
  107 +
  108 + @PreDestroy
  109 + public void shutdownExecutor() {
  110 + if (tsCallBackExecutor != null) {
  111 + tsCallBackExecutor.shutdownNow();
  112 + }
  113 + }
  114 +
  115 + @Override
  116 + public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException {
  117 + edge.setRootRuleChainId(ruleChainId);
  118 + Edge savedEdge = edgeService.saveEdge(edge);
  119 + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null);
  120 + return savedEdge;
  121 + }
  122 +
  123 + private void saveEdgeEvent(TenantId tenantId,
  124 + EdgeId edgeId,
  125 + EdgeEventType type,
  126 + EdgeEventActionType action,
  127 + EntityId entityId,
  128 + JsonNode body) {
  129 + log.debug("Pushing edge event to edge queue. tenantId [{}], edgeId [{}], type [{}], action[{}], entityId [{}], body [{}]",
  130 + tenantId, edgeId, type, action, entityId, body);
  131 +
  132 + EdgeEvent edgeEvent = new EdgeEvent();
  133 + edgeEvent.setEdgeId(edgeId);
  134 + edgeEvent.setTenantId(tenantId);
  135 + edgeEvent.setType(type);
  136 + edgeEvent.setAction(action);
  137 + if (entityId != null) {
  138 + edgeEvent.setEntityId(entityId.getId());
  139 + }
  140 + edgeEvent.setBody(body);
  141 + ListenableFuture<EdgeEvent> future = edgeEventService.saveAsync(edgeEvent);
  142 + Futures.addCallback(future, new FutureCallback<EdgeEvent>() {
  143 + @Override
  144 + public void onSuccess(@Nullable EdgeEvent result) {
  145 + clusterService.onEdgeEventUpdate(tenantId, edgeId);
  146 + }
  147 +
  148 + @Override
  149 + public void onFailure(Throwable t) {
  150 + log.warn("[{}] Can't save edge event [{}] for edge [{}]", tenantId.getId(), edgeEvent, edgeId.getId(), t);
  151 + }
  152 + }, dbCallbackExecutorService);
  153 +
  154 + }
  155 +
  156 + @Override
  157 + public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
  158 + try {
  159 + TenantId tenantId = new TenantId(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
  160 + EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
  161 + switch (type) {
  162 + case EDGE:
  163 + processEdge(tenantId, edgeNotificationMsg);
  164 + break;
  165 + case USER:
  166 + case ASSET:
  167 + case DEVICE:
  168 + case DEVICE_PROFILE:
  169 + case ENTITY_VIEW:
  170 + case DASHBOARD:
  171 + case RULE_CHAIN:
  172 + processEntity(tenantId, edgeNotificationMsg);
  173 + break;
  174 + case CUSTOMER:
  175 + processCustomer(tenantId, edgeNotificationMsg);
  176 + break;
  177 + case WIDGETS_BUNDLE:
  178 + case WIDGET_TYPE:
  179 + processWidgetBundleOrWidgetType(tenantId, edgeNotificationMsg);
  180 + break;
  181 + case ALARM:
  182 + processAlarm(tenantId, edgeNotificationMsg);
  183 + break;
  184 + case RELATION:
  185 + processRelation(tenantId, edgeNotificationMsg);
  186 + break;
  187 + default:
  188 + log.debug("Edge event type [{}] is not designed to be pushed to edge", type);
  189 + }
  190 + } catch (Exception e) {
  191 + callback.onFailure(e);
  192 + log.error("Can't push to edge updates, edgeNotificationMsg [{}]", edgeNotificationMsg, e);
  193 + } finally {
  194 + callback.onSuccess();
  195 + }
  196 + }
  197 +
  198 + private void processEdge(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
  199 + try {
  200 + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
  201 + EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
  202 + ListenableFuture<Edge> edgeFuture;
  203 + switch (actionType) {
  204 + case ASSIGNED_TO_CUSTOMER:
  205 + CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
  206 + edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId);
  207 + Futures.addCallback(edgeFuture, new FutureCallback<Edge>() {
  208 + @Override
  209 + public void onSuccess(@Nullable Edge edge) {
  210 + if (edge != null && !customerId.isNullUid()) {
  211 + saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null);
  212 + PageLink pageLink = new PageLink(DEFAULT_LIMIT);
  213 + PageData<User> pageData;
  214 + do {
  215 + pageData = userService.findCustomerUsers(tenantId, customerId, pageLink);
  216 + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
  217 + log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size());
  218 + for (User user : pageData.getData()) {
  219 + saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null);
  220 + }
  221 + if (pageData.hasNext()) {
  222 + pageLink = pageLink.nextPageLink();
  223 + }
  224 + }
  225 + } while (pageData != null && pageData.hasNext());
  226 + }
  227 + }
  228 +
  229 + @Override
  230 + public void onFailure(Throwable t) {
  231 + log.error("Can't find edge by id [{}]", edgeNotificationMsg, t);
  232 + }
  233 + }, dbCallbackExecutorService);
  234 + break;
  235 + case UNASSIGNED_FROM_CUSTOMER:
  236 + CustomerId customerIdToDelete = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
  237 + edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId);
  238 + Futures.addCallback(edgeFuture, new FutureCallback<Edge>() {
  239 + @Override
  240 + public void onSuccess(@Nullable Edge edge) {
  241 + if (edge != null && !customerIdToDelete.isNullUid()) {
  242 + saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null);
  243 + }
  244 + }
  245 +
  246 + @Override
  247 + public void onFailure(Throwable t) {
  248 + log.error("Can't find edge by id [{}]", edgeNotificationMsg, t);
  249 + }
  250 + }, dbCallbackExecutorService);
  251 + break;
  252 + }
  253 + } catch (Exception e) {
  254 + log.error("Exception during processing edge event", e);
  255 + }
  256 + }
  257 +
  258 + private void processWidgetBundleOrWidgetType(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
  259 + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
  260 + EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
  261 + EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
  262 + switch (actionType) {
  263 + case ADDED:
  264 + case UPDATED:
  265 + case DELETED:
  266 + processActionForAllEdges(tenantId, type, actionType, entityId);
  267 + break;
  268 + }
  269 + }
  270 +
  271 + private void processCustomer(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
  272 + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
  273 + EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
  274 + UUID uuid = new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB());
  275 + CustomerId customerId = new CustomerId(EntityIdFactory.getByEdgeEventTypeAndUuid(type, uuid).getId());
  276 + switch (actionType) {
  277 + case UPDATED:
  278 + PageLink pageLink = new PageLink(DEFAULT_LIMIT);
  279 + PageData<Edge> pageData;
  280 + do {
  281 + pageData = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
  282 + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
  283 + for (Edge edge : pageData.getData()) {
  284 + saveEdgeEvent(tenantId, edge.getId(), type, actionType, customerId, null);
  285 + }
  286 + if (pageData.hasNext()) {
  287 + pageLink = pageLink.nextPageLink();
  288 + }
  289 + }
  290 + } while (pageData != null && pageData.hasNext());
  291 + break;
  292 + case DELETED:
  293 + EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
  294 + saveEdgeEvent(tenantId, edgeId, type, actionType, customerId, null);
  295 + break;
  296 + }
  297 + }
  298 +
  299 + private void processEntity(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
  300 + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction());
  301 + EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
  302 + EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type,
  303 + new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
  304 + EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
  305 + ListenableFuture<List<EdgeId>> edgeIdsFuture;
  306 + switch (actionType) {
  307 + case ADDED: // used only for USER entity
  308 + case UPDATED:
  309 + case CREDENTIALS_UPDATED:
  310 + edgeIdsFuture = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId);
  311 + Futures.addCallback(edgeIdsFuture, new FutureCallback<List<EdgeId>>() {
  312 + @Override
  313 + public void onSuccess(@Nullable List<EdgeId> edgeIds) {
  314 + if (edgeIds != null && !edgeIds.isEmpty()) {
  315 + for (EdgeId edgeId : edgeIds) {
  316 + saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
  317 + }
  318 + }
  319 + }
  320 + @Override
  321 + public void onFailure(Throwable throwable) {
  322 + log.error("Failed to find related edge ids [{}]", edgeNotificationMsg, throwable);
  323 + }
  324 + }, dbCallbackExecutorService);
  325 + break;
  326 + case ASSIGNED_TO_CUSTOMER:
  327 + case UNASSIGNED_FROM_CUSTOMER:
  328 + edgeIdsFuture = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId);
  329 + Futures.addCallback(edgeIdsFuture, new FutureCallback<List<EdgeId>>() {
  330 + @Override
  331 + public void onSuccess(@Nullable List<EdgeId> edgeIds) {
  332 + if (edgeIds != null && !edgeIds.isEmpty()) {
  333 + for (EdgeId edgeId : edgeIds) {
  334 + try {
  335 + CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class);
  336 + ListenableFuture<Edge> future = edgeService.findEdgeByIdAsync(tenantId, edgeId);
  337 + Futures.addCallback(future, new FutureCallback<Edge>() {
  338 + @Override
  339 + public void onSuccess(@Nullable Edge edge) {
  340 + if (edge != null && edge.getCustomerId() != null &&
  341 + !edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(customerId)) {
  342 + saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
  343 + }
  344 + }
  345 + @Override
  346 + public void onFailure(Throwable throwable) {
  347 + log.error("Failed to find edge by id [{}]", edgeNotificationMsg, throwable);
  348 + }
  349 + }, dbCallbackExecutorService);
  350 + } catch (Exception e) {
  351 + log.error("Can't parse customer id from entity body [{}]", edgeNotificationMsg, e);
  352 + }
  353 + }
  354 + }
  355 + }
  356 +
  357 + @Override
  358 + public void onFailure(Throwable throwable) {
  359 + log.error("Failed to find related edge ids [{}]", edgeNotificationMsg, throwable);
  360 + }
  361 + }, dbCallbackExecutorService);
  362 + break;
  363 + case DELETED:
  364 + saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
  365 + break;
  366 + case ASSIGNED_TO_EDGE:
  367 + case UNASSIGNED_FROM_EDGE:
  368 + saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
  369 + if (type.equals(EdgeEventType.RULE_CHAIN)) {
  370 + updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId);
  371 + }
  372 + break;
  373 + }
  374 + }
  375 +
  376 + private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
  377 + PageLink pageLink = new PageLink(DEFAULT_LIMIT);
  378 + PageData<RuleChain> pageData;
  379 + do {
  380 + pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink);
  381 + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
  382 + for (RuleChain ruleChain : pageData.getData()) {
  383 + if (!ruleChain.getId().equals(processingRuleChainId)) {
  384 + List<RuleChainConnectionInfo> connectionInfos =
  385 + ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections();
  386 + if (connectionInfos != null && !connectionInfos.isEmpty()) {
  387 + for (RuleChainConnectionInfo connectionInfo : connectionInfos) {
  388 + if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) {
  389 + saveEdgeEvent(tenantId,
  390 + edgeId,
  391 + EdgeEventType.RULE_CHAIN_METADATA,
  392 + EdgeEventActionType.UPDATED,
  393 + ruleChain.getId(),
  394 + null);
  395 + }
  396 + }
  397 + }
  398 + }
  399 + }
  400 + if (pageData.hasNext()) {
  401 + pageLink = pageLink.nextPageLink();
  402 + }
  403 + }
  404 + } while (pageData != null && pageData.hasNext());
  405 + }
  406 +
  407 + private void processAlarm(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) {
  408 + AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
  409 + ListenableFuture<Alarm> alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId);
  410 + Futures.addCallback(alarmFuture, new FutureCallback<Alarm>() {
  411 + @Override
  412 + public void onSuccess(@Nullable Alarm alarm) {
  413 + if (alarm != null) {
  414 + EdgeEventType type = EdgeUtils.getEdgeEventTypeByEntityType(alarm.getOriginator().getEntityType());
  415 + if (type != null) {
  416 + ListenableFuture<List<EdgeId>> relatedEdgeIdsByEntityIdFuture = edgeService.findRelatedEdgeIdsByEntityId(tenantId, alarm.getOriginator());
  417 + Futures.addCallback(relatedEdgeIdsByEntityIdFuture, new FutureCallback<List<EdgeId>>() {
  418 + @Override
  419 + public void onSuccess(@Nullable List<EdgeId> relatedEdgeIdsByEntityId) {
  420 + if (relatedEdgeIdsByEntityId != null) {
  421 + for (EdgeId edgeId : relatedEdgeIdsByEntityId) {
  422 + saveEdgeEvent(tenantId,
  423 + edgeId,
  424 + EdgeEventType.ALARM,
  425 + EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
  426 + alarmId,
  427 + null);
  428 + }
  429 + }
  430 + }
  431 +
  432 + @Override
  433 + public void onFailure(Throwable t) {
  434 + log.warn("[{}] can't find related edge ids by entity id [{}]", tenantId.getId(), alarm.getOriginator(), t);
  435 + }
  436 + }, dbCallbackExecutorService);
  437 + }
  438 + }
  439 + }
  440 +
  441 + @Override
  442 + public void onFailure(Throwable t) {
  443 + log.warn("[{}] can't find alarm by id [{}]", tenantId.getId(), alarmId.getId(), t);
  444 + }
  445 + }, dbCallbackExecutorService);
  446 + }
  447 +
  448 + private void processRelation(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException {
  449 + EntityRelation relation = mapper.readValue(edgeNotificationMsg.getBody(), EntityRelation.class);
  450 + if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) &&
  451 + !relation.getTo().getEntityType().equals(EntityType.EDGE)) {
  452 + List<ListenableFuture<List<EdgeId>>> futures = new ArrayList<>();
  453 + futures.add(edgeService.findRelatedEdgeIdsByEntityId(tenantId, relation.getTo()));
  454 + futures.add(edgeService.findRelatedEdgeIdsByEntityId(tenantId, relation.getFrom()));
  455 + ListenableFuture<List<List<EdgeId>>> combinedFuture = Futures.allAsList(futures);
  456 + Futures.addCallback(combinedFuture, new FutureCallback<List<List<EdgeId>>>() {
  457 + @Override
  458 + public void onSuccess(@Nullable List<List<EdgeId>> listOfListsEdgeIds) {
  459 + Set<EdgeId> uniqueEdgeIds = new HashSet<>();
  460 + if (listOfListsEdgeIds != null && !listOfListsEdgeIds.isEmpty()) {
  461 + for (List<EdgeId> listOfListsEdgeId : listOfListsEdgeIds) {
  462 + if (listOfListsEdgeId != null) {
  463 + uniqueEdgeIds.addAll(listOfListsEdgeId);
  464 + }
  465 + }
  466 + }
  467 + if (!uniqueEdgeIds.isEmpty()) {
  468 + for (EdgeId edgeId : uniqueEdgeIds) {
  469 + saveEdgeEvent(tenantId,
  470 + edgeId,
  471 + EdgeEventType.RELATION,
  472 + EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()),
  473 + null,
  474 + mapper.valueToTree(relation));
  475 + }
  476 + }
  477 + }
  478 +
  479 + @Override
  480 + public void onFailure(Throwable t) {
  481 + log.warn("[{}] can't find related edge ids by relation to id [{}] and relation from id [{}]" ,
  482 + tenantId.getId(), relation.getTo().getId(), relation.getFrom().getId(), t);
  483 + }
  484 + }, dbCallbackExecutorService);
  485 + }
  486 + }
  487 +
  488 + private void processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) {
  489 + PageLink pageLink = new PageLink(DEFAULT_LIMIT);
  490 + PageData<Edge> pageData;
  491 + do {
  492 + pageData = edgeService.findEdgesByTenantId(tenantId, pageLink);
  493 + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
  494 + for (Edge edge : pageData.getData()) {
  495 + saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, null);
  496 + }
  497 + if (pageData.hasNext()) {
  498 + pageLink = pageLink.nextPageLink();
  499 + }
  500 + }
  501 + } while (pageData != null && pageData.hasNext());
  502 + }
  503 +}
  504 +
  505 +
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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 lombok.Getter;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.context.annotation.Lazy;
  22 +import org.springframework.stereotype.Component;
  23 +import org.thingsboard.server.actors.service.ActorService;
  24 +import org.thingsboard.server.dao.alarm.AlarmService;
  25 +import org.thingsboard.server.dao.asset.AssetService;
  26 +import org.thingsboard.server.dao.attributes.AttributesService;
  27 +import org.thingsboard.server.dao.customer.CustomerService;
  28 +import org.thingsboard.server.dao.dashboard.DashboardService;
  29 +import org.thingsboard.server.dao.device.DeviceCredentialsService;
  30 +import org.thingsboard.server.dao.device.DeviceProfileService;
  31 +import org.thingsboard.server.dao.device.DeviceService;
  32 +import org.thingsboard.server.dao.edge.EdgeEventService;
  33 +import org.thingsboard.server.dao.edge.EdgeService;
  34 +import org.thingsboard.server.dao.entityview.EntityViewService;
  35 +import org.thingsboard.server.dao.relation.RelationService;
  36 +import org.thingsboard.server.dao.rule.RuleChainService;
  37 +import org.thingsboard.server.dao.user.UserService;
  38 +import org.thingsboard.server.dao.widget.WidgetTypeService;
  39 +import org.thingsboard.server.dao.widget.WidgetsBundleService;
  40 +import org.thingsboard.server.queue.discovery.PartitionService;
  41 +import org.thingsboard.server.queue.util.TbCoreComponent;
  42 +import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
  43 +import org.thingsboard.server.service.edge.rpc.constructor.AdminSettingsMsgConstructor;
  44 +import org.thingsboard.server.service.edge.rpc.constructor.AlarmMsgConstructor;
  45 +import org.thingsboard.server.service.edge.rpc.constructor.AssetMsgConstructor;
  46 +import org.thingsboard.server.service.edge.rpc.constructor.CustomerMsgConstructor;
  47 +import org.thingsboard.server.service.edge.rpc.constructor.DashboardMsgConstructor;
  48 +import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor;
  49 +import org.thingsboard.server.service.edge.rpc.constructor.DeviceProfileMsgConstructor;
  50 +import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor;
  51 +import org.thingsboard.server.service.edge.rpc.constructor.EntityViewMsgConstructor;
  52 +import org.thingsboard.server.service.edge.rpc.constructor.RelationMsgConstructor;
  53 +import org.thingsboard.server.service.edge.rpc.constructor.RuleChainMsgConstructor;
  54 +import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor;
  55 +import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstructor;
  56 +import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
  57 +import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService;
  58 +import org.thingsboard.server.service.edge.rpc.processor.AlarmProcessor;
  59 +import org.thingsboard.server.service.edge.rpc.processor.DeviceProcessor;
  60 +import org.thingsboard.server.service.edge.rpc.processor.RelationProcessor;
  61 +import org.thingsboard.server.service.edge.rpc.processor.TelemetryProcessor;
  62 +import org.thingsboard.server.service.executors.DbCallbackExecutorService;
  63 +import org.thingsboard.server.service.queue.TbClusterService;
  64 +import org.thingsboard.server.service.state.DeviceStateService;
  65 +
  66 +@Component
  67 +@TbCoreComponent
  68 +@Data
  69 +public class EdgeContextComponent {
  70 +
  71 + @Lazy
  72 + @Autowired
  73 + private EdgeService edgeService;
  74 +
  75 + @Autowired
  76 + private PartitionService partitionService;
  77 +
  78 + @Lazy
  79 + @Autowired
  80 + private EdgeEventService edgeEventService;
  81 +
  82 + @Lazy
  83 + @Autowired
  84 + private AssetService assetService;
  85 +
  86 + @Lazy
  87 + @Autowired
  88 + private DeviceService deviceService;
  89 +
  90 + @Lazy
  91 + @Autowired
  92 + private DeviceProfileService deviceProfileService;
  93 +
  94 + @Lazy
  95 + @Autowired
  96 + private DeviceCredentialsService deviceCredentialsService;
  97 +
  98 + @Lazy
  99 + @Autowired
  100 + private EntityViewService entityViewService;
  101 +
  102 + @Lazy
  103 + @Autowired
  104 + private AttributesService attributesService;
  105 +
  106 + @Lazy
  107 + @Autowired
  108 + private CustomerService customerService;
  109 +
  110 + @Lazy
  111 + @Autowired
  112 + private RelationService relationService;
  113 +
  114 + @Lazy
  115 + @Autowired
  116 + private AlarmService alarmService;
  117 +
  118 + @Lazy
  119 + @Autowired
  120 + private DashboardService dashboardService;
  121 +
  122 + @Lazy
  123 + @Autowired
  124 + private RuleChainService ruleChainService;
  125 +
  126 + @Lazy
  127 + @Autowired
  128 + private UserService userService;
  129 +
  130 + @Lazy
  131 + @Autowired
  132 + private ActorService actorService;
  133 +
  134 + @Lazy
  135 + @Autowired
  136 + private WidgetsBundleService widgetsBundleService;
  137 +
  138 + @Lazy
  139 + @Autowired
  140 + private WidgetTypeService widgetTypeService;
  141 +
  142 + @Lazy
  143 + @Autowired
  144 + private DeviceStateService deviceStateService;
  145 +
  146 + @Lazy
  147 + @Autowired
  148 + private TbClusterService tbClusterService;
  149 +
  150 + @Lazy
  151 + @Autowired
  152 + private SyncEdgeService syncEdgeService;
  153 +
  154 + @Lazy
  155 + @Autowired
  156 + private RuleChainMsgConstructor ruleChainMsgConstructor;
  157 +
  158 + @Lazy
  159 + @Autowired
  160 + private AlarmMsgConstructor alarmMsgConstructor;
  161 +
  162 + @Lazy
  163 + @Autowired
  164 + private DeviceMsgConstructor deviceMsgConstructor;
  165 +
  166 + @Lazy
  167 + @Autowired
  168 + private DeviceProfileMsgConstructor deviceProfileMsgConstructor;
  169 +
  170 + @Lazy
  171 + @Autowired
  172 + private AssetMsgConstructor assetMsgConstructor;
  173 +
  174 + @Lazy
  175 + @Autowired
  176 + private EntityViewMsgConstructor entityViewMsgConstructor;
  177 +
  178 + @Lazy
  179 + @Autowired
  180 + private DashboardMsgConstructor dashboardMsgConstructor;
  181 +
  182 + @Lazy
  183 + @Autowired
  184 + private CustomerMsgConstructor customerMsgConstructor;
  185 +
  186 + @Lazy
  187 + @Autowired
  188 + private UserMsgConstructor userMsgConstructor;
  189 +
  190 + @Lazy
  191 + @Autowired
  192 + private RelationMsgConstructor relationMsgConstructor;
  193 +
  194 + @Lazy
  195 + @Autowired
  196 + private WidgetsBundleMsgConstructor widgetsBundleMsgConstructor;
  197 +
  198 + @Lazy
  199 + @Autowired
  200 + private WidgetTypeMsgConstructor widgetTypeMsgConstructor;
  201 +
  202 + @Lazy
  203 + @Autowired
  204 + private AdminSettingsMsgConstructor adminSettingsMsgConstructor;
  205 +
  206 + @Lazy
  207 + @Autowired
  208 + private EntityDataMsgConstructor entityDataMsgConstructor;
  209 +
  210 + @Lazy
  211 + @Autowired
  212 + private AlarmProcessor alarmProcessor;
  213 +
  214 + @Lazy
  215 + @Autowired
  216 + private DeviceProcessor deviceProcessor;
  217 +
  218 + @Lazy
  219 + @Autowired
  220 + private RelationProcessor relationProcessor;
  221 +
  222 + @Lazy
  223 + @Autowired
  224 + private TelemetryProcessor telemetryProcessor;
  225 +
  226 + @Lazy
  227 + @Autowired
  228 + private EdgeEventStorageSettings edgeEventStorageSettings;
  229 +
  230 + @Autowired
  231 + @Getter
  232 + private DbCallbackExecutorService dbCallbackExecutor;
  233 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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 org.thingsboard.server.common.data.edge.Edge;
  19 +import org.thingsboard.server.common.data.id.RuleChainId;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.msg.queue.TbCallback;
  22 +import org.thingsboard.server.gen.transport.TransportProtos;
  23 +
  24 +import java.io.IOException;
  25 +
  26 +public interface EdgeNotificationService {
  27 +
  28 + Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException;
  29 +
  30 + void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback);
  31 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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 +
  19 +import lombok.Data;
  20 +import org.springframework.beans.factory.annotation.Value;
  21 +import org.springframework.stereotype.Component;
  22 +
  23 +@Component
  24 +@Data
  25 +public class EdgeEventStorageSettings {
  26 + @Value("${edges.storage.max_read_records_count}")
  27 + private int maxReadRecordsCount;
  28 + @Value("${edges.storage.no_read_records_sleep}")
  29 + private long noRecordsSleepInterval;
  30 + @Value("${edges.storage.sleep_between_batches}")
  31 + private long sleepIntervalBetweenBatches;
  32 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2021 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 com.google.common.util.concurrent.FutureCallback;
  21 +import io.grpc.Server;
  22 +import io.grpc.netty.NettyServerBuilder;
  23 +import io.grpc.stub.StreamObserver;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.beans.factory.annotation.Autowired;
  26 +import org.springframework.beans.factory.annotation.Value;
  27 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  28 +import org.springframework.stereotype.Service;
  29 +import org.thingsboard.common.util.ThingsBoardThreadFactory;
  30 +import org.thingsboard.server.common.data.DataConstants;
  31 +import org.thingsboard.server.common.data.Tenant;
  32 +import org.thingsboard.server.common.data.edge.Edge;
  33 +import org.thingsboard.server.common.data.id.EdgeId;
  34 +import org.thingsboard.server.common.data.id.TenantId;
  35 +import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  36 +import org.thingsboard.server.common.data.kv.BooleanDataEntry;
  37 +import org.thingsboard.server.common.data.kv.LongDataEntry;
  38 +import org.thingsboard.server.gen.edge.EdgeRpcServiceGrpc;
  39 +import org.thingsboard.server.gen.edge.RequestMsg;
  40 +import org.thingsboard.server.gen.edge.ResponseMsg;
  41 +import org.thingsboard.server.queue.util.TbCoreComponent;
  42 +import org.thingsboard.server.service.edge.EdgeContextComponent;
  43 +import org.thingsboard.server.service.state.DefaultDeviceStateService;
  44 +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
  45 +
  46 +import javax.annotation.Nullable;
  47 +import javax.annotation.PostConstruct;
  48 +import javax.annotation.PreDestroy;
  49 +import java.io.File;
  50 +import java.io.IOException;
  51 +import java.util.Collections;
  52 +import java.util.Map;
  53 +import java.util.UUID;
  54 +import java.util.concurrent.ConcurrentHashMap;
  55 +import java.util.concurrent.ConcurrentMap;
  56 +import java.util.concurrent.Executors;
  57 +import java.util.concurrent.ScheduledExecutorService;
  58 +import java.util.concurrent.ScheduledFuture;
  59 +import java.util.concurrent.TimeUnit;
  60 +
  61 +@Service
  62 +@Slf4j
  63 +@ConditionalOnProperty(prefix = "edges", value = "enabled", havingValue = "true")
  64 +@TbCoreComponent
  65 +public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase implements EdgeRpcService {
  66 +
  67 + private final ConcurrentMap<EdgeId, EdgeGrpcSession> sessions = new ConcurrentHashMap<>();
  68 + private final ConcurrentMap<EdgeId, Boolean> sessionNewEvents = new ConcurrentHashMap<>();
  69 + private final ConcurrentMap<EdgeId, ScheduledFuture<?>> sessionEdgeEventChecks = new ConcurrentHashMap<>();
  70 + private static final ObjectMapper mapper = new ObjectMapper();
  71 +
  72 + @Value("${edges.rpc.port}")
  73 + private int rpcPort;
  74 + @Value("${edges.rpc.ssl.enabled}")
  75 + private boolean sslEnabled;
  76 + @Value("${edges.rpc.ssl.cert}")
  77 + private String certFileResource;
  78 + @Value("${edges.rpc.ssl.private_key}")
  79 + private String privateKeyResource;
  80 + @Value("${edges.state.persistToTelemetry:false}")
  81 + private boolean persistToTelemetry;
  82 + @Value("${edges.rpc.client_max_keep_alive_time_sec}")
  83 + private int clientMaxKeepAliveTimeSec;
  84 +
  85 + @Value("${edges.scheduler_pool_size}")
  86 + private int schedulerPoolSize;
  87 +
  88 + @Autowired
  89 + private EdgeContextComponent ctx;
  90 +
  91 + @Autowired
  92 + private TelemetrySubscriptionService tsSubService;
  93 +
  94 + private Server server;
  95 +
  96 + private ScheduledExecutorService scheduler;
  97 +
  98 + @PostConstruct
  99 + public void init() {
  100 + log.info("Initializing Edge RPC service!");
  101 + NettyServerBuilder builder = NettyServerBuilder.forPort(rpcPort)
  102 + .permitKeepAliveTime(clientMaxKeepAliveTimeSec, TimeUnit.SECONDS)
  103 + .addService(this);
  104 + if (sslEnabled) {
  105 + try {
  106 + File certFile = new File(Resources.getResource(certFileResource).toURI());
  107 + File privateKeyFile = new File(Resources.getResource(privateKeyResource).toURI());
  108 + builder.useTransportSecurity(certFile, privateKeyFile);
  109 + } catch (Exception e) {
  110 + log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e);
  111 + throw new RuntimeException("Unable to set up SSL context!", e);
  112 + }
  113 + }
  114 + server = builder.build();
  115 + log.info("Going to start Edge RPC server using port: {}", rpcPort);
  116 + try {
  117 + server.start();
  118 + } catch (IOException e) {
  119 + log.error("Failed to start Edge RPC server!", e);
  120 + throw new RuntimeException("Failed to start Edge RPC server!");
  121 + }
  122 + this.scheduler = Executors.newScheduledThreadPool(schedulerPoolSize, ThingsBoardThreadFactory.forName("edge-scheduler"));
  123 + log.info("Edge RPC service initialized!");
  124 + }
  125 +
  126 + @PreDestroy
  127 + public void destroy() {
  128 + if (server != null) {
  129 + server.shutdownNow();
  130 + }
  131 + for (Map.Entry<EdgeId, ScheduledFuture<?>> entry : sessionEdgeEventChecks.entrySet()) {
  132 + EdgeId edgeId = entry.getKey();
  133 + ScheduledFuture<?> sessionEdgeEventCheck = entry.getValue();
  134 + if (sessionEdgeEventCheck != null && !sessionEdgeEventCheck.isCancelled() && !sessionEdgeEventCheck.isDone()) {
  135 + sessionEdgeEventCheck.cancel(true);
  136 + sessionEdgeEventChecks.remove(edgeId);
  137 + }
  138 + }
  139 + if (scheduler != null) {
  140 + scheduler.shutdownNow();
  141 + }
  142 + }
  143 +
  144 + @Override
  145 + public StreamObserver<RequestMsg> handleMsgs(StreamObserver<ResponseMsg> outputStream) {
  146 + return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, mapper).getInputStream();
  147 + }
  148 +
  149 + @Override
  150 + public void updateEdge(Edge edge) {
  151 + EdgeGrpcSession session = sessions.get(edge.getId());
  152 + if (session != null && session.isConnected()) {
  153 + log.debug("[{}] Updating configuration for edge [{}] [{}]", edge.getTenantId(), edge.getName(), edge.getId());
  154 + session.onConfigurationUpdate(edge);
  155 + } else {
  156 + log.debug("[{}] Session doesn't exist for edge [{}] [{}]", edge.getTenantId(), edge.getName(), edge.getId());
  157 + }
  158 + }
  159 +
  160 + @Override
  161 + public void deleteEdge(EdgeId edgeId) {
  162 + EdgeGrpcSession session = sessions.get(edgeId);
  163 + if (session != null && session.isConnected()) {
  164 + log.info("Closing and removing session for edge [{}]", edgeId);
  165 + session.close();
  166 + sessions.remove(edgeId);
  167 + sessionNewEvents.remove(edgeId);
  168 + cancelScheduleEdgeEventsCheck(edgeId);
  169 + }
  170 + }
  171 +
  172 + @Override
  173 + public void onEdgeEvent(EdgeId edgeId) {
  174 + log.trace("[{}] onEdgeEvent", edgeId.getId());
  175 + if (!sessionNewEvents.get(edgeId)) {
  176 + log.trace("[{}] set session new events flag to true", edgeId.getId());
  177 + sessionNewEvents.put(edgeId, true);
  178 + }
  179 + }
  180 +
  181 + private void onEdgeConnect(EdgeId edgeId, EdgeGrpcSession edgeGrpcSession) {
  182 + log.info("[{}] edge [{}] connected successfully.", edgeGrpcSession.getSessionId(), edgeId);
  183 + sessions.put(edgeId, edgeGrpcSession);
  184 + sessionNewEvents.put(edgeId, false);
  185 + save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true);
  186 + save(edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, System.currentTimeMillis());
  187 + cancelScheduleEdgeEventsCheck(edgeId);
  188 + scheduleEdgeEventsCheck(edgeGrpcSession);
  189 + }
  190 +
  191 + public EdgeGrpcSession getEdgeGrpcSessionById(TenantId tenantId, EdgeId edgeId) {
  192 + EdgeGrpcSession session = sessions.get(edgeId);
  193 + if (session != null && session.isConnected()) {
  194 + return session;
  195 + } else {
  196 + log.error("[{}] Edge is not connected [{}]", tenantId, edgeId);
  197 + throw new RuntimeException("Edge is not connected");
  198 + }
  199 + }
  200 +
  201 + private void scheduleEdgeEventsCheck(EdgeGrpcSession session) {
  202 + EdgeId edgeId = session.getEdge().getId();
  203 + UUID tenantId = session.getEdge().getTenantId().getId();
  204 + if (sessions.containsKey(edgeId)) {
  205 + ScheduledFuture<?> schedule = scheduler.schedule(() -> {
  206 + try {
  207 + if (sessionNewEvents.get(edgeId)) {
  208 + log.trace("[{}] Set session new events flag to false", edgeId.getId());
  209 + sessionNewEvents.put(edgeId, false);
  210 + session.processEdgeEvents();
  211 + }
  212 + } catch (Exception e) {
  213 + log.warn("[{}] Failed to process edge events for edge [{}]!", tenantId, session.getEdge().getId().getId(), e);
  214 + }
  215 + scheduleEdgeEventsCheck(session);
  216 + }, ctx.getEdgeEventStorageSettings().getNoRecordsSleepInterval(), TimeUnit.MILLISECONDS);
  217 + sessionEdgeEventChecks.put(edgeId, schedule);
  218 + log.trace("[{}] Check edge event scheduled for edge [{}]", tenantId, edgeId.getId());
  219 + } else {
  220 + log.debug("[{}] Session was removed and edge event check schedule must not be started [{}]",
  221 + tenantId, edgeId.getId());
  222 + }
  223 + }
  224 +
  225 + private void cancelScheduleEdgeEventsCheck(EdgeId edgeId) {
  226 + log.trace("[{}] cancelling edge event check for edge", edgeId);
  227 + if (sessionEdgeEventChecks.containsKey(edgeId)) {
  228 + ScheduledFuture<?> sessionEdgeEventCheck = sessionEdgeEventChecks.get(edgeId);
  229 + if (sessionEdgeEventCheck != null && !sessionEdgeEventCheck.isCancelled() && !sessionEdgeEventCheck.isDone()) {
  230 + sessionEdgeEventCheck.cancel(true);
  231 + sessionEdgeEventChecks.remove(edgeId);
  232 + }
  233 + }
  234 + }
  235 +
  236 + private void onEdgeDisconnect(EdgeId edgeId) {
  237 + log.info("[{}] edge disconnected!", edgeId);
  238 + sessions.remove(edgeId);
  239 + sessionNewEvents.remove(edgeId);
  240 + save(edgeId, DefaultDeviceStateService.ACTIVITY_STATE, false);
  241 + save(edgeId, DefaultDeviceStateService.LAST_DISCONNECT_TIME, System.currentTimeMillis());
  242 + cancelScheduleEdgeEventsCheck(edgeId);
  243 + }
  244 +
  245 + private void save(EdgeId edgeId, String key, long value) {
  246 + log.debug("[{}] Updating long edge telemetry [{}] [{}]", edgeId, key, value);
  247 + if (persistToTelemetry) {
  248 + tsSubService.saveAndNotify(
  249 + TenantId.SYS_TENANT_ID, edgeId,
  250 + Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry(key, value))),
  251 + new AttributeSaveCallback(edgeId, key, value));
  252 + } else {
  253 + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, edgeId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(edgeId, key, value));
  254 + }
  255 + }
  256 +
  257 + private void save(EdgeId edgeId, String key, boolean value) {
  258 + log.debug("[{}] Updating boolean edge telemetry [{}] [{}]", edgeId, key, value);
  259 + if (persistToTelemetry) {
  260 + tsSubService.saveAndNotify(
  261 + TenantId.SYS_TENANT_ID, edgeId,
  262 + Collections.singletonList(new BasicTsKvEntry(System.currentTimeMillis(), new BooleanDataEntry(key, value))),
  263 + new AttributeSaveCallback(edgeId, key, value));
  264 + } else {
  265 + tsSubService.saveAttrAndNotify(TenantId.SYS_TENANT_ID, edgeId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(edgeId, key, value));
  266 + }
  267 + }
  268 +
  269 + private static class AttributeSaveCallback implements FutureCallback<Void> {
  270 + private final EdgeId edgeId;
  271 + private final String key;
  272 + private final Object value;
  273 +
  274 + AttributeSaveCallback(EdgeId edgeId, String key, Object value) {
  275 + this.edgeId = edgeId;
  276 + this.key = key;
  277 + this.value = value;
  278 + }
  279 +
  280 + @Override
  281 + public void onSuccess(@Nullable Void result) {
  282 + log.trace("[{}] Successfully updated attribute [{}] with value [{}]", edgeId, key, value);
  283 + }
  284 +
  285 + @Override
  286 + public void onFailure(Throwable t) {
  287 + log.warn("[{}] Failed to update attribute [{}] with value [{}]", edgeId, key, value, t);
  288 + }
  289 + }
  290 +}
... ...