Commit 711ff2ab53af0e07d07535342eaca271db6b2bcc

Authored by Yura
2 parents 3f7f257d aa6d3fba

Merge remote-tracking branch 'origin/develop/1.5' into develop/cluter-refactoring

Showing 70 changed files with 2452 additions and 851 deletions

Too many changes to show.

To preserve performance only 70 of 269 files are displayed.

... ... @@ -14,7 +14,7 @@
14 14 # limitations under the License.
15 15 #
16 16
17   -export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@"
  17 +export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@"
18 18 export LOG_FILENAME=${pkg.name}.out
19 19 export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions
20 20 export SQL_DATA_FOLDER=${pkg.installFolder}/data/sql
... ...
1   -{
2   - "apiToken": "messaging",
3   - "name": "Demo Device Messaging RPC Plugin",
4   - "clazz": "org.thingsboard.server.extensions.core.plugin.messaging.DeviceMessagingPlugin",
5   - "publicAccess": false,
6   - "state": "ACTIVE",
7   - "configuration": {
8   - "maxDeviceCountPerCustomer": 1024,
9   - "defaultTimeout": 20000,
10   - "maxTimeout": 60000
11   - },
12   - "additionalInfo": null
13   -}
\ No newline at end of file
1   -{
2   - "apiToken": "mail",
3   - "name": "Demo Email Plugin",
4   - "clazz": "org.thingsboard.server.extensions.core.plugin.mail.MailPlugin",
5   - "publicAccess": true,
6   - "state": "ACTIVE",
7   - "configuration": {
8   - "host": "smtp.sendgrid.net",
9   - "port": 2525,
10   - "username": "apikey",
11   - "password": "your_api_key",
12   - "otherProperties": [
13   - {
14   - "key": "mail.smtp.auth",
15   - "value": "true"
16   - },
17   - {
18   - "key": "mail.smtp.timeout",
19   - "value": "10000"
20   - },
21   - {
22   - "key": "mail.smtp.starttls.enable",
23   - "value": "true"
24   - }
25   - ]
26   - },
27   - "additionalInfo": null
28   -}
\ No newline at end of file
1   -{
2   - "apiToken": "time",
3   - "name": "Demo Time RPC Plugin",
4   - "clazz": "org.thingsboard.server.extensions.core.plugin.time.TimePlugin",
5   - "publicAccess": false,
6   - "state": "ACTIVE",
7   - "configuration": {
8   - "timeFormat": "yyyy MM dd HH:mm:ss.SSS"
9   - },
10   - "additionalInfo": null
11   -}
\ No newline at end of file
1   -{
2   - "name": "Demo Alarm Rule",
3   - "state": "ACTIVE",
4   - "weight": 0,
5   - "pluginToken": "mail",
6   - "filters": [
7   - {
8   - "clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter",
9   - "name": "MsgTypeFilter",
10   - "configuration": {
11   - "messageTypes": [
12   - "POST_TELEMETRY",
13   - "POST_ATTRIBUTES",
14   - "GET_ATTRIBUTES"
15   - ]
16   - }
17   - },
18   - {
19   - "clazz": "org.thingsboard.server.extensions.core.filter.DeviceTelemetryFilter",
20   - "name": "Temperature filter",
21   - "configuration": {
22   - "filter": "typeof temperature !== 'undefined' && temperature >= 100"
23   - }
24   - }
25   - ],
26   - "processor": {
27   - "clazz": "org.thingsboard.server.extensions.core.processor.AlarmDeduplicationProcessor",
28   - "name": "AlarmDeduplicationProcessor",
29   - "configuration": {
30   - "alarmIdTemplate": "[$date.get('yyyy-MM-dd HH:mm')] Device $cs.get('serialNumber')($cs.get('model')) temperature is high!",
31   - "alarmBodyTemplate": "[$date.get('yyyy-MM-dd HH:mm:ss')] Device $cs.get('serialNumber')($cs.get('model')) temperature is $temp.valueAsString!"
32   - }
33   - },
34   - "action": {
35   - "clazz": "org.thingsboard.server.extensions.core.action.mail.SendMailAction",
36   - "name": "Send Mail Action",
37   - "configuration": {
38   - "sendFlag": "isNewAlarm",
39   - "fromTemplate": "thingsboard@gmail.com",
40   - "toTemplate": "thingsboard@gmail.com",
41   - "subjectTemplate": "$alarmId",
42   - "bodyTemplate": "$alarmBody"
43   - }
44   - },
45   - "additionalInfo": null
46   -}
\ No newline at end of file
1   -{
2   - "name": "Demo getTime RPC Rule",
3   - "state": "ACTIVE",
4   - "weight": 0,
5   - "pluginToken": "time",
6   - "filters": [
7   - {
8   - "configuration": {
9   - "messageTypes": [
10   - "RPC_REQUEST"
11   - ]
12   - },
13   - "name": "RPC Request Filter",
14   - "clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter"
15   - },
16   - {
17   - "configuration": {
18   - "methodNames": [
19   - {
20   - "name": "getTime"
21   - }
22   - ]
23   - },
24   - "name": "getTime method filter",
25   - "clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter"
26   - }
27   - ],
28   - "processor": null,
29   - "action": {
30   - "configuration": {},
31   - "clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction",
32   - "name": "getTimeAction"
33   - },
34   - "additionalInfo": null
35   -}
\ No newline at end of file
1   -{
2   - "name": "Demo Messaging RPC Rule",
3   - "state": "ACTIVE",
4   - "weight": 0,
5   - "pluginToken": "messaging",
6   - "filters": [
7   - {
8   - "configuration": {
9   - "messageTypes": [
10   - "RPC_REQUEST"
11   - ]
12   - },
13   - "name": "RPC Request Filter",
14   - "clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter"
15   - },
16   - {
17   - "configuration": {
18   - "methodNames": [
19   - {
20   - "name": "getDevices"
21   - },
22   - {
23   - "name": "sendMsg"
24   - }
25   - ]
26   - },
27   - "name": "Messaging methods filter",
28   - "clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter"
29   - }
30   - ],
31   - "processor": null,
32   - "action": {
33   - "configuration": {},
34   - "clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction",
35   - "name": "Messaging RPC Action"
36   - },
37   - "additionalInfo": null
38   -}
\ No newline at end of file
1   -{
2   - "apiToken": "rpc",
3   - "name": "System RPC Plugin",
4   - "clazz": "org.thingsboard.server.extensions.core.plugin.rpc.RpcPlugin",
5   - "publicAccess": true,
6   - "state": "ACTIVE",
7   - "configuration": {
8   - "defaultTimeout": 20000
9   - },
10   - "additionalInfo": null
11   -}
\ No newline at end of file
1   -{
2   - "apiToken": "telemetry",
3   - "name": "System Telemetry Plugin",
4   - "clazz": "org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin",
5   - "publicAccess": true,
6   - "state": "ACTIVE",
7   - "configuration": {},
8   - "additionalInfo": null
9   -}
\ No newline at end of file
1   -{
2   - "name": "System Telemetry Rule",
3   - "state": "ACTIVE",
4   - "weight": 0,
5   - "pluginToken": "telemetry",
6   - "filters": [
7   - {
8   - "clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter",
9   - "name": "TelemetryFilter",
10   - "configuration": {
11   - "messageTypes": [
12   - "POST_TELEMETRY",
13   - "POST_ATTRIBUTES",
14   - "GET_ATTRIBUTES"
15   - ]
16   - }
17   - }
18   - ],
19   - "processor": null,
20   - "action": {
21   - "clazz": "org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction",
22   - "name": "TelemetryMsgConverterAction",
23   - "configuration": {
24   - "timeUnit": "DAYS",
25   - "ttlValue": 365
26   - }
27   - },
28   - "additionalInfo": null
29   -}
\ No newline at end of file
  1 +{
  2 + "ruleChain": {
  3 + "additionalInfo": null,
  4 + "name": "Root Rule Chain",
  5 + "firstRuleNodeId": null,
  6 + "root": true,
  7 + "debugMode": false,
  8 + "configuration": null
  9 + },
  10 + "metadata": {
  11 + "firstNodeIndex": 2,
  12 + "nodes": [
  13 + {
  14 + "additionalInfo": {
  15 + "layoutX": 824,
  16 + "layoutY": 156
  17 + },
  18 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode",
  19 + "name": "SaveTS",
  20 + "debugMode": true,
  21 + "configuration": {
  22 + "defaultTTL": 0
  23 + }
  24 + },
  25 + {
  26 + "additionalInfo": {
  27 + "layoutX": 825,
  28 + "layoutY": 52
  29 + },
  30 + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode",
  31 + "name": "save client attributes",
  32 + "debugMode": true,
  33 + "configuration": {
  34 + "scope": "CLIENT_SCOPE"
  35 + }
  36 + },
  37 + {
  38 + "additionalInfo": {
  39 + "layoutX": 347,
  40 + "layoutY": 149
  41 + },
  42 + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode",
  43 + "name": "Message Type Switch",
  44 + "debugMode": false,
  45 + "configuration": {
  46 + "version": 0
  47 + }
  48 + },
  49 + {
  50 + "additionalInfo": {
  51 + "layoutX": 825,
  52 + "layoutY": 266
  53 + },
  54 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  55 + "name": "Log RPC",
  56 + "debugMode": false,
  57 + "configuration": {
  58 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  59 + }
  60 + },
  61 + {
  62 + "additionalInfo": {
  63 + "layoutX": 825,
  64 + "layoutY": 379
  65 + },
  66 + "type": "org.thingsboard.rule.engine.action.TbLogNode",
  67 + "name": "Log Other",
  68 + "debugMode": false,
  69 + "configuration": {
  70 + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"
  71 + }
  72 + }
  73 + ],
  74 + "connections": [
  75 + {
  76 + "fromIndex": 2,
  77 + "toIndex": 4,
  78 + "type": "Other"
  79 + },
  80 + {
  81 + "fromIndex": 2,
  82 + "toIndex": 1,
  83 + "type": "Post attributes"
  84 + },
  85 + {
  86 + "fromIndex": 2,
  87 + "toIndex": 0,
  88 + "type": "Post telemetry"
  89 + },
  90 + {
  91 + "fromIndex": 2,
  92 + "toIndex": 3,
  93 + "type": "RPC Request"
  94 + }
  95 + ],
  96 + "ruleChainConnections": null
  97 + }
  98 +}
\ No newline at end of file
... ...
... ... @@ -84,6 +84,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_sear
84 84
85 85 CREATE TABLE IF NOT EXISTS thingsboard.rule_node (
86 86 id uuid,
  87 + rule_chain_id uuid,
87 88 type text,
88 89 name text,
89 90 debug_mode boolean,
... ...
... ... @@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS rule_chain (
28 28
29 29 CREATE TABLE IF NOT EXISTS rule_node (
30 30 id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY,
  31 + rule_chain_id varchar(31),
31 32 additional_info varchar,
32 33 configuration varchar(10000000),
33 34 type varchar(255),
... ...
... ... @@ -49,6 +49,7 @@ import org.thingsboard.server.dao.customer.CustomerService;
49 49 import org.thingsboard.server.dao.device.DeviceService;
50 50 import org.thingsboard.server.dao.event.EventService;
51 51 import org.thingsboard.server.dao.plugin.PluginService;
  52 +import org.thingsboard.server.dao.queue.MsgQueue;
52 53 import org.thingsboard.server.dao.relation.RelationService;
53 54 import org.thingsboard.server.dao.rule.RuleChainService;
54 55 import org.thingsboard.server.dao.rule.RuleService;
... ... @@ -60,8 +61,11 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
60 61 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
61 62 import org.thingsboard.server.service.component.ComponentDiscoveryService;
62 63 import org.thingsboard.server.service.executors.DbCallbackExecutorService;
  64 +import org.thingsboard.server.service.executors.ExternalCallExecutorService;
63 65 import org.thingsboard.server.service.mail.MailExecutorService;
  66 +import org.thingsboard.server.service.rpc.DeviceRpcService;
64 67 import org.thingsboard.server.service.script.JsExecutorService;
  68 +import org.thingsboard.server.service.state.DeviceStateService;
65 69 import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
66 70
67 71 import java.io.IOException;
... ... @@ -163,6 +167,10 @@ public class ActorSystemContext {
163 167
164 168 @Autowired
165 169 @Getter
  170 + private DeviceRpcService deviceRpcService;
  171 +
  172 + @Autowired
  173 + @Getter
166 174 @Setter
167 175 private PluginWebSocketMsgEndpoint wsMsgEndpoint;
168 176
... ... @@ -180,23 +188,35 @@ public class ActorSystemContext {
180 188
181 189 @Autowired
182 190 @Getter
  191 + private ExternalCallExecutorService externalCallExecutorService;
  192 +
  193 + @Autowired
  194 + @Getter
183 195 private MailService mailService;
184 196
  197 + @Autowired
  198 + @Getter
  199 + private MsgQueue msgQueue;
  200 +
  201 + @Autowired
  202 + @Getter
  203 + private DeviceStateService deviceStateService;
  204 +
185 205 @Value("${actors.session.sync.timeout}")
186 206 @Getter
187 207 private long syncSessionTimeout;
188 208
189   - @Value("${actors.plugin.termination.delay}")
  209 + @Value("${actors.queue.enabled}")
190 210 @Getter
191   - private long pluginActorTerminationDelay;
  211 + private boolean queuePersistenceEnabled;
192 212
193   - @Value("${actors.plugin.processing.timeout}")
  213 + @Value("${actors.queue.timeout}")
194 214 @Getter
195   - private long pluginProcessingTimeout;
  215 + private long queuePersistenceTimeout;
196 216
197   - @Value("${actors.plugin.error_persist_frequency}")
  217 + @Value("${actors.client_side_rpc.timeout}")
198 218 @Getter
199   - private long pluginErrorPersistFrequency;
  219 + private long clientSideRpcTimeout;
200 220
201 221 @Value("${actors.rule.chain.error_persist_frequency}")
202 222 @Getter
... ... @@ -206,14 +226,6 @@ public class ActorSystemContext {
206 226 @Getter
207 227 private long ruleNodeErrorPersistFrequency;
208 228
209   - @Value("${actors.rule.termination.delay}")
210   - @Getter
211   - private long ruleActorTerminationDelay;
212   -
213   - @Value("${actors.rule.error_persist_frequency}")
214   - @Getter
215   - private long ruleErrorPersistFrequency;
216   -
217 229 @Value("${actors.statistics.enabled}")
218 230 @Getter
219 231 private boolean statisticsEnabled;
... ...
... ... @@ -29,13 +29,12 @@ import org.thingsboard.server.actors.shared.plugin.SystemPluginManager;
29 29 import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager;
30 30 import org.thingsboard.server.actors.tenant.TenantActor;
31 31 import org.thingsboard.server.common.data.Tenant;
32   -import org.thingsboard.server.common.data.id.PluginId;
33   -import org.thingsboard.server.common.data.id.RuleChainId;
34 32 import org.thingsboard.server.common.data.id.TenantId;
35 33 import org.thingsboard.server.common.data.page.PageDataIterable;
36 34 import org.thingsboard.server.common.msg.TbActorMsg;
37   -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
38   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  35 +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
  36 +import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
  37 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
39 38 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
40 39 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
41 40 import org.thingsboard.server.dao.model.ModelConstants;
... ... @@ -90,12 +89,23 @@ public class AppActor extends RuleChainManagerActor {
90 89 @Override
91 90 protected boolean process(TbActorMsg msg) {
92 91 switch (msg.getMsgType()) {
  92 + case CLUSTER_EVENT_MSG:
  93 + broadcast(msg);
  94 + break;
93 95 case COMPONENT_LIFE_CYCLE_MSG:
94 96 onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
95 97 break;
96 98 case SERVICE_TO_RULE_ENGINE_MSG:
97 99 onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
98 100 break;
  101 + case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
  102 + case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
  103 + case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
  104 + case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
  105 + case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
  106 + case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
  107 + onToDeviceActorMsg((TenantAwareMsg) msg);
  108 + break;
99 109 default:
100 110 return false;
101 111 }
... ... @@ -110,48 +120,12 @@ public class AppActor extends RuleChainManagerActor {
110 120 }
111 121 }
112 122
113   -
114   -// @Override
115   -// public void onReceive(Object msg) throws Exception {
116   -// logger.debug("Received message: {}", msg);
117   -// if (msg instanceof ToDeviceActorMsg) {
118   -// processDeviceMsg((ToDeviceActorMsg) msg);
119   -// } else if (msg instanceof ToPluginActorMsg) {
120   -// onToPluginMsg((ToPluginActorMsg) msg);
121   -// } else if (msg instanceof ToDeviceActorNotificationMsg) {
122   -// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
123   -// } else if (msg instanceof Terminated) {
124   -// processTermination((Terminated) msg);
125   -// } else if (msg instanceof ClusterEventMsg) {
126   -// broadcast(msg);
127   -// } else if (msg instanceof ComponentLifecycleMsg) {
128   -// onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
129   -// } else if (msg instanceof PluginTerminationMsg) {
130   -// onPluginTerminated((PluginTerminationMsg) msg);
131   -// } else {
132   -// logger.warning("Unknown message: {}!", msg);
133   -// }
134   -// }
135   -
136   - private void onPluginTerminated(PluginTerminationMsg msg) {
137   - pluginManager.remove(msg.getId());
138   - }
139   -
140   - private void broadcast(Object msg) {
141   - pluginManager.broadcast(msg);
  123 + @Override
  124 + protected void broadcast(Object msg) {
  125 + super.broadcast(msg);
142 126 tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
143 127 }
144 128
145   - private void onToPluginMsg(ToPluginActorMsg msg) {
146   - ActorRef target;
147   - if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) {
148   - target = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
149   - } else {
150   - target = getOrCreateTenantActor(msg.getPluginTenantId());
151   - }
152   - target.tell(msg, ActorRef.noSender());
153   - }
154   -
155 129 private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
156 130 ActorRef target;
157 131 if (SYSTEM_TENANT.equals(msg.getTenantId())) {
... ... @@ -166,17 +140,17 @@ public class AppActor extends RuleChainManagerActor {
166 140 }
167 141 }
168 142
169   - private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
  143 + private void onToDeviceActorMsg(TenantAwareMsg msg) {
170 144 getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender());
171 145 }
172 146
173   - private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) {
174   - TenantId tenantId = toDeviceActorMsg.getTenantId();
  147 + private void processDeviceMsg(DeviceToDeviceActorMsg deviceToDeviceActorMsg) {
  148 + TenantId tenantId = deviceToDeviceActorMsg.getTenantId();
175 149 ActorRef tenantActor = getOrCreateTenantActor(tenantId);
176   - if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
177   -// tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
  150 + if (deviceToDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) {
  151 +// tenantActor.tell(new RuleChainDeviceMsg(deviceToDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self());
178 152 } else {
179   - tenantActor.tell(toDeviceActorMsg, context().self());
  153 + tenantActor.tell(deviceToDeviceActorMsg, context().self());
180 154 }
181 155 }
182 156
... ...
... ... @@ -24,59 +24,66 @@ import org.thingsboard.server.common.data.id.DeviceId;
24 24 import org.thingsboard.server.common.data.id.TenantId;
25 25 import org.thingsboard.server.common.msg.TbActorMsg;
26 26 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
27   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  27 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
  28 +import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
  29 +import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg;
  30 +import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
28 31 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
29   -import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
30 32 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
31   -import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
32   -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
33   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
  33 +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
  34 +import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
34 35
35 36 public class DeviceActor extends ContextAwareActor {
36 37
37 38 private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this);
38 39
39   - private final TenantId tenantId;
40   - private final DeviceId deviceId;
41 40 private final DeviceActorMessageProcessor processor;
42 41
43 42 private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) {
44 43 super(systemContext);
45   - this.tenantId = tenantId;
46   - this.deviceId = deviceId;
47   - this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId);
  44 + this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId);
48 45 }
49 46
50 47 @Override
51 48 protected boolean process(TbActorMsg msg) {
52   - return false;
53   - }
54   -
55   - @Override
56   - public void onReceive(Object msg) throws Exception {
57   -// if (msg instanceof RuleChainDeviceMsg) {
58   -// processor.process(context(), (RuleChainDeviceMsg) msg);
59   -// } else if (msg instanceof RulesProcessedMsg) {
60   -// processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg);
61   - if (msg instanceof ToDeviceActorMsg) {
62   - processor.process(context(), (ToDeviceActorMsg) msg);
63   - } else if (msg instanceof ToDeviceActorNotificationMsg) {
64   - if (msg instanceof DeviceAttributesEventNotificationMsg) {
  49 + switch (msg.getMsgType()) {
  50 + case CLUSTER_EVENT_MSG:
  51 + processor.processClusterEventMsg((ClusterEventMsg) msg);
  52 + break;
  53 + case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
  54 + processor.process(context(), (DeviceToDeviceActorMsg) msg);
  55 + break;
  56 + case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
65 57 processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg);
66   - } else if (msg instanceof ToDeviceRpcRequestPluginMsg) {
67   - processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg);
68   - } else if (msg instanceof DeviceCredentialsUpdateNotificationMsg){
  58 + break;
  59 + case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
69 60 processor.processCredentialsUpdate();
70   - } else if (msg instanceof DeviceNameOrTypeUpdateMsg){
  61 + break;
  62 + case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
71 63 processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg);
72   - }
73   - } else if (msg instanceof TimeoutMsg) {
74   - processor.processTimeout(context(), (TimeoutMsg) msg);
75   - } else if (msg instanceof ClusterEventMsg) {
76   - processor.processClusterEventMsg((ClusterEventMsg) msg);
77   - } else {
78   - logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName());
  64 + break;
  65 + case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
  66 + processor.processRpcRequest(context(), (ToDeviceRpcRequestActorMsg) msg);
  67 + break;
  68 + case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
  69 + processor.processToServerRPCResponse(context(), (ToServerRpcResponseActorMsg) msg);
  70 + break;
  71 + case DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG:
  72 + processor.processServerSideRpcTimeout(context(), (DeviceActorServerSideRpcTimeoutMsg) msg);
  73 + break;
  74 + case DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG:
  75 + processor.processClientSideRpcTimeout(context(), (DeviceActorClientSideRpcTimeoutMsg) msg);
  76 + break;
  77 + case DEVICE_ACTOR_QUEUE_TIMEOUT_MSG:
  78 + processor.processQueueTimeout(context(), (DeviceActorQueueTimeoutMsg) msg);
  79 + break;
  80 + case RULE_ENGINE_QUEUE_PUT_ACK_MSG:
  81 + processor.processQueueAck(context(), (RuleEngineQueuePutAckMsg) msg);
  82 + break;
  83 + default:
  84 + return false;
79 85 }
  86 + return true;
80 87 }
81 88
82 89 public static class ActorCreator extends ContextBasedCreator<DeviceActor> {
... ...
... ... @@ -18,30 +18,76 @@ package org.thingsboard.server.actors.device;
18 18 import akka.actor.ActorContext;
19 19 import akka.actor.ActorRef;
20 20 import akka.event.LoggingAdapter;
  21 +import com.datastax.driver.core.utils.UUIDs;
  22 +import com.google.common.util.concurrent.FutureCallback;
  23 +import com.google.common.util.concurrent.Futures;
  24 +import com.google.common.util.concurrent.ListenableFuture;
  25 +import com.google.gson.Gson;
  26 +import com.google.gson.JsonArray;
  27 +import com.google.gson.JsonObject;
  28 +import com.google.gson.JsonParser;
21 29 import org.thingsboard.server.actors.ActorSystemContext;
22 30 import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
23 31 import org.thingsboard.server.common.data.DataConstants;
24 32 import org.thingsboard.server.common.data.Device;
25 33 import org.thingsboard.server.common.data.id.DeviceId;
26 34 import org.thingsboard.server.common.data.id.SessionId;
  35 +import org.thingsboard.server.common.data.id.TenantId;
27 36 import org.thingsboard.server.common.data.kv.AttributeKey;
28 37 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  38 +import org.thingsboard.server.common.data.kv.KvEntry;
  39 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
  40 +import org.thingsboard.server.common.msg.TbMsg;
  41 +import org.thingsboard.server.common.msg.TbMsgDataType;
  42 +import org.thingsboard.server.common.msg.TbMsgMetaData;
29 43 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
30 44 import org.thingsboard.server.common.msg.cluster.ServerAddress;
31   -import org.thingsboard.server.common.msg.core.*;
32   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  45 +import org.thingsboard.server.common.msg.core.AttributesUpdateNotification;
  46 +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest;
  47 +import org.thingsboard.server.common.msg.core.BasicCommandAckResponse;
  48 +import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse;
  49 +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
  50 +import org.thingsboard.server.common.msg.core.BasicToDeviceSessionActorMsg;
  51 +import org.thingsboard.server.common.msg.core.GetAttributesRequest;
  52 +import org.thingsboard.server.common.msg.core.RuleEngineError;
  53 +import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg;
  54 +import org.thingsboard.server.common.msg.core.SessionCloseMsg;
  55 +import org.thingsboard.server.common.msg.core.SessionCloseNotification;
  56 +import org.thingsboard.server.common.msg.core.SessionOpenMsg;
  57 +import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
  58 +import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg;
  59 +import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg;
  60 +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
  61 +import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg;
  62 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
33 63 import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg;
  64 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
34 65 import org.thingsboard.server.common.msg.session.FromDeviceMsg;
35   -import org.thingsboard.server.common.msg.session.MsgType;
  66 +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
  67 +import org.thingsboard.server.common.msg.session.SessionMsgType;
36 68 import org.thingsboard.server.common.msg.session.SessionType;
37 69 import org.thingsboard.server.common.msg.session.ToDeviceMsg;
38   -import org.thingsboard.server.extensions.api.device.DeviceAttributes;
  70 +import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
  71 +import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg;
  72 +import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
39 73 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
40 74 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
41   -import org.thingsboard.server.extensions.api.plugins.msg.*;
42   -
43   -import java.util.*;
44   -import java.util.concurrent.ExecutionException;
  75 +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
  76 +import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
  77 +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
  78 +import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg;
  79 +
  80 +import javax.annotation.Nullable;
  81 +import java.util.ArrayList;
  82 +import java.util.Arrays;
  83 +import java.util.Collections;
  84 +import java.util.HashMap;
  85 +import java.util.HashSet;
  86 +import java.util.List;
  87 +import java.util.Map;
  88 +import java.util.Optional;
  89 +import java.util.Set;
  90 +import java.util.UUID;
45 91 import java.util.concurrent.TimeoutException;
46 92 import java.util.function.Consumer;
47 93 import java.util.function.Predicate;
... ... @@ -52,46 +98,46 @@ import java.util.stream.Collectors;
52 98 */
53 99 public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
54 100
  101 + private final TenantId tenantId;
55 102 private final DeviceId deviceId;
56 103 private final Map<SessionId, SessionInfo> sessions;
57 104 private final Map<SessionId, SessionInfo> attributeSubscriptions;
58 105 private final Map<SessionId, SessionInfo> rpcSubscriptions;
  106 + private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap;
  107 + private final Map<Integer, ToServerRpcRequestMetadata> toServerRpcPendingMap;
  108 + private final Map<UUID, PendingSessionMsgData> pendingMsgs;
59 109
60   - private final Map<Integer, ToDeviceRpcRequestMetadata> rpcPendingMap;
  110 + private final Gson gson = new Gson();
  111 + private final JsonParser jsonParser = new JsonParser();
61 112
62 113 private int rpcSeq = 0;
63 114 private String deviceName;
64 115 private String deviceType;
65   - private DeviceAttributes deviceAttributes;
  116 + private TbMsgMetaData defaultMetaData;
66 117
67   - public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) {
  118 + public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) {
68 119 super(systemContext, logger);
  120 + this.tenantId = tenantId;
69 121 this.deviceId = deviceId;
70 122 this.sessions = new HashMap<>();
71 123 this.attributeSubscriptions = new HashMap<>();
72 124 this.rpcSubscriptions = new HashMap<>();
73   - this.rpcPendingMap = new HashMap<>();
  125 + this.toDeviceRpcPendingMap = new HashMap<>();
  126 + this.toServerRpcPendingMap = new HashMap<>();
  127 + this.pendingMsgs = new HashMap<>();
74 128 initAttributes();
75 129 }
76 130
77 131 private void initAttributes() {
78   - //TODO: add invalidation of deviceType cache.
79 132 Device device = systemContext.getDeviceService().findDeviceById(deviceId);
80 133 this.deviceName = device.getName();
81 134 this.deviceType = device.getType();
82   - this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE),
83   - fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE));
84   - }
85   -
86   - private void refreshAttributes(DeviceAttributesEventNotificationMsg msg) {
87   - if (msg.isDeleted()) {
88   - msg.getDeletedKeys().forEach(key -> deviceAttributes.remove(key));
89   - } else {
90   - deviceAttributes.update(msg.getScope(), msg.getValues());
91   - }
  135 + this.defaultMetaData = new TbMsgMetaData();
  136 + this.defaultMetaData.putValue("deviceName", deviceName);
  137 + this.defaultMetaData.putValue("deviceType", deviceType);
92 138 }
93 139
94   - void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) {
  140 + void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {
95 141 ToDeviceRpcRequest request = msg.getMsg();
96 142 ToDeviceRpcRequestBody body = request.getBody();
97 143 ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
... ... @@ -118,9 +164,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
118 164 syncSessionSet.forEach(rpcSubscriptions::remove);
119 165
120 166 if (request.isOneway() && sent) {
121   - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null);
122   - context.parent().tell(responsePluginMsg, ActorRef.noSender());
123 167 logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
  168 + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
124 169 } else {
125 170 registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
126 171 }
... ... @@ -132,24 +177,46 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
132 177
133 178 }
134 179
135   - private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
136   - rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
137   - TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout);
  180 + private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
  181 + toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent));
  182 + DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout);
138 183 scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout());
139 184 }
140 185
141   - public void processTimeout(ActorContext context, TimeoutMsg msg) {
142   - ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId());
  186 + void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) {
  187 + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
143 188 if (requestMd != null) {
144 189 logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
145   - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION);
146   - context.parent().tell(responsePluginMsg, ActorRef.noSender());
  190 + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
  191 + null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
  192 + }
  193 + }
  194 +
  195 + void processQueueTimeout(ActorContext context, DeviceActorQueueTimeoutMsg msg) {
  196 + PendingSessionMsgData data = pendingMsgs.remove(msg.getId());
  197 + if (data != null) {
  198 + logger.debug("[{}] Queue put [{}] timeout detected!", deviceId, msg.getId());
  199 + ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(data.getSessionMsgType(), RuleEngineError.QUEUE_PUT_TIMEOUT);
  200 + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress());
  201 + }
  202 + }
  203 +
  204 + void processQueueAck(ActorContext context, RuleEngineQueuePutAckMsg msg) {
  205 + PendingSessionMsgData data = pendingMsgs.remove(msg.getId());
  206 + if (data != null && data.isReplyOnQueueAck()) {
  207 + int remainingAcks = data.getAckMsgCount() - 1;
  208 + data.setAckMsgCount(remainingAcks);
  209 + logger.debug("[{}] Queue put [{}] ack detected. Remaining acks: {}!", deviceId, msg.getId(), remainingAcks);
  210 + if (remainingAcks == 0) {
  211 + ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onSuccess(data.getSessionMsgType(), data.getRequestId());
  212 + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress());
  213 + }
147 214 }
148 215 }
149 216
150 217 private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional<ServerAddress> server) {
151   - if (!rpcPendingMap.isEmpty()) {
152   - logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), sessionId);
  218 + if (!toDeviceRpcPendingMap.isEmpty()) {
  219 + logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId);
153 220 if (type == SessionType.SYNC) {
154 221 logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId);
155 222 rpcSubscriptions.remove(sessionId);
... ... @@ -159,12 +226,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
159 226 }
160 227 Set<Integer> sentOneWayIds = new HashSet<>();
161 228 if (type == SessionType.ASYNC) {
162   - rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
  229 + toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds));
163 230 } else {
164   - rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
  231 + toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds));
165 232 }
166 233
167   - sentOneWayIds.forEach(rpcPendingMap::remove);
  234 + sentOneWayIds.forEach(toDeviceRpcPendingMap::remove);
168 235 }
169 236
170 237 private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, SessionId sessionId, Optional<ServerAddress> server, Set<Integer> sentOneWayIds) {
... ... @@ -173,8 +240,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
173 240 ToDeviceRpcRequestBody body = request.getBody();
174 241 if (request.isOneway()) {
175 242 sentOneWayIds.add(entry.getKey());
176   - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null);
177   - context.parent().tell(responsePluginMsg, ActorRef.noSender());
  243 + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(request.getId(), null, null));
178 244 }
179 245 ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg(
180 246 entry.getKey(),
... ... @@ -186,14 +252,170 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
186 252 };
187 253 }
188 254
189   - void process(ActorContext context, ToDeviceActorMsg msg) {
  255 + void process(ActorContext context, DeviceToDeviceActorMsg msg) {
190 256 processSubscriptionCommands(context, msg);
191 257 processRpcResponses(context, msg);
192 258 processSessionStateMsgs(msg);
  259 +
  260 + SessionMsgType sessionMsgType = msg.getPayload().getMsgType();
  261 + if (sessionMsgType.requiresRulesProcessing()) {
  262 + switch (sessionMsgType) {
  263 + case GET_ATTRIBUTES_REQUEST:
  264 + handleGetAttributesRequest(msg);
  265 + break;
  266 + case POST_ATTRIBUTES_REQUEST:
  267 + handlePostAttributesRequest(context, msg);
  268 + reportActivity();
  269 + break;
  270 + case POST_TELEMETRY_REQUEST:
  271 + handlePostTelemetryRequest(context, msg);
  272 + reportActivity();
  273 + break;
  274 + case TO_SERVER_RPC_REQUEST:
  275 + handleClientSideRPCRequest(context, msg);
  276 + reportActivity();
  277 + break;
  278 + }
  279 + }
  280 + }
  281 +
  282 + private void reportActivity() {
  283 + systemContext.getDeviceStateService().onDeviceActivity(deviceId);
  284 + }
  285 +
  286 + private void reportSessionOpen() {
  287 + systemContext.getDeviceStateService().onDeviceConnect(deviceId);
  288 + }
  289 +
  290 + private void reportSessionClose() {
  291 + systemContext.getDeviceStateService().onDeviceDisconnect(deviceId);
  292 + }
  293 +
  294 + private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) {
  295 + GetAttributesRequest request = (GetAttributesRequest) src.getPayload();
  296 + ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, request.getClientAttributeNames());
  297 + ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, request.getClientAttributeNames());
  298 +
  299 + Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() {
  300 + @Override
  301 + public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {
  302 + BasicGetAttributesResponse response = BasicGetAttributesResponse.onSuccess(request.getMsgType(),
  303 + request.getRequestId(), BasicAttributeKVMsg.from(result.get(0), result.get(1)));
  304 + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, src.getSessionId()), src.getServerAddress());
  305 + }
  306 +
  307 + @Override
  308 + public void onFailure(Throwable t) {
  309 + if (t instanceof Exception) {
  310 + ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onError(SessionMsgType.GET_ATTRIBUTES_REQUEST, request.getRequestId(), (Exception) t);
  311 + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, src.getSessionId()), src.getServerAddress());
  312 + } else {
  313 + logger.error("[{}] Failed to process attributes request", deviceId, t);
  314 + }
  315 + }
  316 + });
  317 + }
  318 +
  319 + private ListenableFuture<List<AttributeKvEntry>> getAttributeKvEntries(DeviceId deviceId, String scope, Optional<Set<String>> names) {
  320 + if (names.isPresent()) {
  321 + if (!names.get().isEmpty()) {
  322 + return systemContext.getAttributesService().find(deviceId, scope, names.get());
  323 + } else {
  324 + return systemContext.getAttributesService().findAll(deviceId, scope);
  325 + }
  326 + } else {
  327 + return Futures.immediateFuture(Collections.emptyList());
  328 + }
  329 + }
  330 +
  331 + private void handlePostAttributesRequest(ActorContext context, DeviceToDeviceActorMsg src) {
  332 + AttributesUpdateRequest request = (AttributesUpdateRequest) src.getPayload();
  333 +
  334 + JsonObject json = new JsonObject();
  335 + for (AttributeKvEntry kv : request.getAttributes()) {
  336 + kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
  337 + kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
  338 + kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
  339 + kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v));
  340 + }
  341 +
  342 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
  343 + PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
  344 + SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1);
  345 + pushToRuleEngineWithTimeout(context, tbMsg, msgData);
  346 + }
  347 +
  348 + private void handlePostTelemetryRequest(ActorContext context, DeviceToDeviceActorMsg src) {
  349 + TelemetryUploadRequest request = (TelemetryUploadRequest) src.getPayload();
  350 +
  351 + Map<Long, List<KvEntry>> tsData = request.getData();
  352 +
  353 + PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(),
  354 + SessionMsgType.POST_TELEMETRY_REQUEST, request.getRequestId(), true, tsData.size());
  355 +
  356 + for (Map.Entry<Long, List<KvEntry>> entry : tsData.entrySet()) {
  357 + JsonObject json = new JsonObject();
  358 + json.addProperty("ts", entry.getKey());
  359 + JsonObject values = new JsonObject();
  360 + for (KvEntry kv : entry.getValue()) {
  361 + kv.getBooleanValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
  362 + kv.getLongValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
  363 + kv.getDoubleValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
  364 + kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v));
  365 + }
  366 + json.add("values", values);
  367 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
  368 + pushToRuleEngineWithTimeout(context, tbMsg, msgData);
  369 + }
  370 + }
  371 +
  372 + private void handleClientSideRPCRequest(ActorContext context, DeviceToDeviceActorMsg src) {
  373 + ToServerRpcRequestMsg request = (ToServerRpcRequestMsg) src.getPayload();
  374 +
  375 + JsonObject json = new JsonObject();
  376 + json.addProperty("method", request.getMethod());
  377 + json.add("params", jsonParser.parse(request.getParams()));
  378 +
  379 + TbMsgMetaData requestMetaData = defaultMetaData.copy();
  380 + requestMetaData.putValue("requestId", Integer.toString(request.getRequestId()));
  381 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, requestMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L);
  382 + PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), SessionMsgType.TO_SERVER_RPC_REQUEST, request.getRequestId(), false, 1);
  383 + pushToRuleEngineWithTimeout(context, tbMsg, msgData);
  384 +
  385 + scheduleMsgWithDelay(context, new DeviceActorClientSideRpcTimeoutMsg(request.getRequestId(), systemContext.getClientSideRpcTimeout()), systemContext.getClientSideRpcTimeout());
  386 + toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(src.getSessionId(), src.getSessionType(), src.getServerAddress()));
  387 + }
  388 +
  389 + public void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) {
  390 + ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId());
  391 + if (data != null) {
  392 + logger.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId());
  393 + ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(SessionMsgType.TO_SERVER_RPC_REQUEST, RuleEngineError.TIMEOUT);
  394 + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServer());
  395 + }
  396 + }
  397 +
  398 + void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) {
  399 + ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId());
  400 + if (data != null) {
  401 + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(msg.getMsg(), data.getSessionId()), data.getServer());
  402 + }
  403 + }
  404 +
  405 + private void pushToRuleEngineWithTimeout(ActorContext context, TbMsg tbMsg, PendingSessionMsgData pendingMsgData) {
  406 + SessionMsgType sessionMsgType = pendingMsgData.getSessionMsgType();
  407 + int requestId = pendingMsgData.getRequestId();
  408 + if (systemContext.isQueuePersistenceEnabled()) {
  409 + pendingMsgs.put(tbMsg.getId(), pendingMsgData);
  410 + scheduleMsgWithDelay(context, new DeviceActorQueueTimeoutMsg(tbMsg.getId(), systemContext.getQueuePersistenceTimeout()), systemContext.getQueuePersistenceTimeout());
  411 + } else {
  412 + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(BasicStatusCodeResponse.onSuccess(sessionMsgType, requestId), pendingMsgData.getSessionId());
  413 + sendMsgToSessionActor(response, pendingMsgData.getServerAddress());
  414 + }
  415 + context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self());
193 416 }
194 417
195 418 void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
196   - refreshAttributes(msg);
197 419 if (attributeSubscriptions.size() > 0) {
198 420 ToDeviceMsg notification = null;
199 421 if (msg.isDeleted()) {
... ... @@ -223,50 +445,29 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
223 445 }
224 446 }
225 447
226   -// void process(ActorContext context, RuleChainDeviceMsg srcMsg) {
227   -// ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(),
228   -// srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self());
229   -// ChainProcessingContext ctx = new ChainProcessingContext(md);
230   -// if (ctx.getChainLength() > 0) {
231   -// RuleProcessingMsg msg = new RuleProcessingMsg(ctx);
232   -// ActorRef ruleActorRef = ctx.getCurrentActor();
233   -// ruleActorRef.tell(msg, ActorRef.noSender());
234   -// } else {
235   -// context.self().tell(new RulesProcessedMsg(ctx), context.self());
236   -// }
237   -// }
238   -
239   - void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) {
  448 + private void processRpcResponses(ActorContext context, DeviceToDeviceActorMsg msg) {
240 449 SessionId sessionId = msg.getSessionId();
241 450 FromDeviceMsg inMsg = msg.getPayload();
242   - if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) {
  451 + if (inMsg.getMsgType() == SessionMsgType.TO_DEVICE_RPC_RESPONSE) {
243 452 logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId);
244 453 ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg;
245   - ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId());
  454 + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
246 455 boolean success = requestMd != null;
247 456 if (success) {
248   - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData());
249   - Optional<ServerAddress> pluginServerAddress = requestMd.getMsg().getServerAddress();
250   - if (pluginServerAddress.isPresent()) {
251   - systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg);
252   - logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
253   - } else {
254   - context.parent().tell(responsePluginMsg, ActorRef.noSender());
255   - logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
256   - }
  457 + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), responseMsg.getData(), null));
257 458 } else {
258 459 logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
259 460 }
260 461 if (msg.getSessionType() == SessionType.SYNC) {
261 462 BasicCommandAckResponse response = success
262   - ? BasicCommandAckResponse.onSuccess(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
263   - : BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
  463 + ? BasicCommandAckResponse.onSuccess(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId())
  464 + : BasicCommandAckResponse.onError(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException());
264 465 sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress());
265 466 }
266 467 }
267 468 }
268 469
269   - public void processClusterEventMsg(ClusterEventMsg msg) {
  470 + void processClusterEventMsg(ClusterEventMsg msg) {
270 471 if (!msg.isAdded()) {
271 472 logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress());
272 473 Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer()
... ... @@ -276,69 +477,43 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
276 477 }
277 478 }
278 479
279   - private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) {
280   - return toPluginRpcResponseMsg(requestMsg, data, null);
281   - }
282   -
283   - private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) {
284   - return toPluginRpcResponseMsg(requestMsg, null, error);
285   - }
286   -
287   - private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) {
288   - return new ToPluginRpcResponseDeviceMsg(
289   - requestMsg.getPluginId(),
290   - requestMsg.getPluginTenantId(),
291   - new FromDeviceRpcResponse(requestMsg.getMsg().getId(),
292   - data,
293   - error
294   - )
295   - );
296   - }
297   -
298   -// void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) {
299   -// ChainProcessingContext ctx = msg.getCtx();
300   -// ToDeviceActorMsg inMsg = ctx.getInMsg();
301   -// SessionId sid = inMsg.getSessionId();
302   -// ToDeviceSessionActorMsg response;
303   -// if (ctx.getResponse() != null) {
304   -// response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid);
305   -// } else {
306   -// response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid);
307   -// }
308   -// sendMsgToSessionActor(response, inMsg.getServerAddress());
309   -// }
310   -
311   - private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) {
  480 + private void processSubscriptionCommands(ActorContext context, DeviceToDeviceActorMsg msg) {
312 481 SessionId sessionId = msg.getSessionId();
313 482 SessionType sessionType = msg.getSessionType();
314 483 FromDeviceMsg inMsg = msg.getPayload();
315   - if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
  484 + if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) {
316 485 logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
317 486 attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
318   - } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
  487 + } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) {
319 488 logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
320 489 attributeSubscriptions.remove(sessionId);
321   - } else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
  490 + } else if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) {
322 491 logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
323 492 rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress()));
324 493 sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress());
325   - } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
  494 + } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) {
326 495 logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType);
327 496 rpcSubscriptions.remove(sessionId);
328 497 }
329 498 }
330 499
331   - private void processSessionStateMsgs(ToDeviceActorMsg msg) {
  500 + private void processSessionStateMsgs(DeviceToDeviceActorMsg msg) {
332 501 SessionId sessionId = msg.getSessionId();
333 502 FromDeviceMsg inMsg = msg.getPayload();
334 503 if (inMsg instanceof SessionOpenMsg) {
335 504 logger.debug("[{}] Processing new session [{}]", deviceId, sessionId);
336 505 sessions.put(sessionId, new SessionInfo(SessionType.ASYNC, msg.getServerAddress()));
  506 + if (sessions.size() == 1) {
  507 + reportSessionOpen();
  508 + }
337 509 } else if (inMsg instanceof SessionCloseMsg) {
338 510 logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
339 511 sessions.remove(sessionId);
340 512 attributeSubscriptions.remove(sessionId);
341 513 rpcSubscriptions.remove(sessionId);
  514 + if (sessions.isEmpty()) {
  515 + reportSessionClose();
  516 + }
342 517 }
343 518 }
344 519
... ... @@ -352,17 +527,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
352 527 }
353 528 }
354 529
355   - private List<AttributeKvEntry> fetchAttributes(String scope) {
356   - try {
357   - //TODO: replace this with async operation. Happens only during actor creation, but is still criticla for performance,
358   - return systemContext.getAttributesService().findAll(this.deviceId, scope).get();
359   - } catch (InterruptedException | ExecutionException e) {
360   - logger.warning("[{}] Failed to fetch attributes for scope: {}", deviceId, scope);
361   - throw new RuntimeException(e);
362   - }
363   - }
364   -
365   - public void processCredentialsUpdate() {
  530 + void processCredentialsUpdate() {
366 531 sessions.forEach((k, v) -> {
367 532 sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(new SessionCloseNotification(), k), v.getServer());
368 533 });
... ... @@ -370,8 +535,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
370 535 rpcSubscriptions.clear();
371 536 }
372 537
373   - public void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
  538 + void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
374 539 this.deviceName = msg.getDeviceName();
375 540 this.deviceType = msg.getDeviceType();
  541 + this.defaultMetaData = new TbMsgMetaData();
  542 + this.defaultMetaData.putValue("deviceName", deviceName);
  543 + this.defaultMetaData.putValue("deviceType", deviceType);
376 544 }
  545 +
377 546 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.actors.device;
  17 +
  18 +import akka.actor.ActorRef;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.id.TenantId;
  21 +import org.thingsboard.server.common.msg.MsgType;
  22 +import org.thingsboard.server.common.msg.TbActorMsg;
  23 +import org.thingsboard.server.common.msg.TbMsg;
  24 +
  25 +/**
  26 + * Created by ashvayka on 15.03.18.
  27 + */
  28 +@Data
  29 +public final class DeviceActorToRuleEngineMsg implements TbActorMsg {
  30 +
  31 + private final ActorRef callbackRef;
  32 + private final TbMsg tbMsg;
  33 +
  34 + @Override
  35 + public MsgType getMsgType() {
  36 + return MsgType.DEVICE_ACTOR_TO_RULE_ENGINE_MSG;
  37 + }
  38 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.actors.device;
  17 +
  18 +import lombok.AllArgsConstructor;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.id.SessionId;
  21 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  22 +import org.thingsboard.server.common.msg.session.SessionMsgType;
  23 +
  24 +import java.util.Optional;
  25 +
  26 +/**
  27 + * Created by ashvayka on 17.04.18.
  28 + */
  29 +@Data
  30 +@AllArgsConstructor
  31 +public final class PendingSessionMsgData {
  32 +
  33 + private final SessionId sessionId;
  34 + private final Optional<ServerAddress> serverAddress;
  35 + private final SessionMsgType sessionMsgType;
  36 + private final int requestId;
  37 + private final boolean replyOnQueueAck;
  38 + private int ackMsgCount;
  39 +
  40 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.actors.device;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.msg.MsgType;
  20 +import org.thingsboard.server.common.msg.TbActorMsg;
  21 +
  22 +import java.util.UUID;
  23 +
  24 +/**
  25 + * Created by ashvayka on 15.03.18.
  26 + */
  27 +@Data
  28 +public final class RuleEngineQueuePutAckMsg implements TbActorMsg {
  29 +
  30 + private final UUID id;
  31 +
  32 + @Override
  33 + public MsgType getMsgType() {
  34 + return MsgType.RULE_ENGINE_QUEUE_PUT_ACK_MSG;
  35 + }
  36 +}
... ...
... ... @@ -16,13 +16,13 @@
16 16 package org.thingsboard.server.actors.device;
17 17
18 18 import lombok.Data;
19   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
  19 +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
20 20
21 21 /**
22 22 * @author Andrew Shvayka
23 23 */
24 24 @Data
25 25 public class ToDeviceRpcRequestMetadata {
26   - private final ToDeviceRpcRequestPluginMsg msg;
  26 + private final ToDeviceRpcRequestActorMsg msg;
27 27 private final boolean sent;
28 28 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.actors.device;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.server.common.data.id.SessionId;
  20 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  21 +import org.thingsboard.server.common.msg.session.SessionType;
  22 +
  23 +import java.util.Optional;
  24 +
  25 +/**
  26 + * @author Andrew Shvayka
  27 + */
  28 +@Data
  29 +public class ToServerRpcRequestMetadata {
  30 + private final SessionId sessionId;
  31 + private final SessionType type;
  32 + private final Optional<ServerAddress> server;
  33 +}
... ...
... ... @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId;
26 26 import org.thingsboard.server.common.msg.TbActorMsg;
27 27 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
28 28 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
29   -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  29 +import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
30 30 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
31 31 import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
32 32 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
... ... @@ -153,6 +153,7 @@ public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProc
153 153
154 154 @Override
155 155 protected long getErrorPersistFrequency() {
156   - return systemContext.getPluginErrorPersistFrequency();
  156 + return 0;
  157 +// return systemContext.getPluginErrorPersistFrequency();
157 158 }
158 159 }
... ...
... ... @@ -30,13 +30,14 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
30 30 import org.thingsboard.server.common.msg.cluster.ServerAddress;
31 31 import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse;
32 32 import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg;
33   -import org.thingsboard.server.common.msg.session.MsgType;
  33 +import org.thingsboard.server.common.msg.session.SessionMsgType;
  34 +import org.thingsboard.server.common.msg.session.SessionMsgType;
34 35 import org.thingsboard.server.extensions.api.plugins.Plugin;
35 36 import org.thingsboard.server.extensions.api.plugins.PluginInitializationException;
36 37 import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
37 38 import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg;
38 39 import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
39   -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
  40 +import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
40 41 import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg;
41 42 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
42 43 import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg;
... ... @@ -108,7 +109,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
108 109 } catch (Exception ex) {
109 110 logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex);
110 111 RuleToPluginMsg ruleMsg = msg.getMsg();
111   - MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR;
  112 + SessionMsgType responceMsgType = SessionMsgType.RULE_ENGINE_ERROR;
112 113 Integer requestId = 0;
113 114 if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) {
114 115 requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId();
... ... @@ -216,7 +217,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId>
216 217 @Override
217 218 public void onStop(ActorContext context) {
218 219 onStop();
219   - scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
  220 +// scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay());
220 221 }
221 222
222 223 private void onStop() {
... ...
... ... @@ -36,9 +36,12 @@ import org.thingsboard.server.common.data.page.TextPageLink;
36 36 import org.thingsboard.server.common.data.plugin.PluginMetaData;
37 37 import org.thingsboard.server.common.data.relation.EntityRelation;
38 38 import org.thingsboard.server.common.data.relation.RelationTypeGroup;
  39 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
39 40 import org.thingsboard.server.common.data.rule.RuleChain;
40 41 import org.thingsboard.server.common.data.rule.RuleMetaData;
41 42 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  43 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  44 +import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
42 45 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
43 46 import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext;
44 47 import org.thingsboard.server.extensions.api.plugins.PluginCallback;
... ... @@ -348,7 +351,7 @@ public final class PluginProcessingContext implements PluginContext {
348 351 throw new IllegalStateException("Not Implemented!");
349 352 }
350 353 } else {
351   - callback.onSuccess(this, ValidationResult.ok());
  354 + callback.onSuccess(this, ValidationResult.ok(null));
352 355 }
353 356 }
354 357
... ... @@ -366,7 +369,7 @@ public final class PluginProcessingContext implements PluginContext {
366 369 } else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) {
367 370 return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
368 371 } else {
369   - return ValidationResult.ok();
  372 + return ValidationResult.ok(null);
370 373 }
371 374 }
372 375 }));
... ... @@ -387,7 +390,7 @@ public final class PluginProcessingContext implements PluginContext {
387 390 } else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) {
388 391 return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
389 392 } else {
390   - return ValidationResult.ok();
  393 + return ValidationResult.ok(null);
391 394 }
392 395 }
393 396 }));
... ... @@ -408,7 +411,7 @@ public final class PluginProcessingContext implements PluginContext {
408 411 } else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) {
409 412 return ValidationResult.accessDenied("Rule is not in system scope!");
410 413 } else {
411   - return ValidationResult.ok();
  414 + return ValidationResult.ok(null);
412 415 }
413 416 }
414 417 }));
... ... @@ -429,7 +432,7 @@ public final class PluginProcessingContext implements PluginContext {
429 432 } else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
430 433 return ValidationResult.accessDenied("Rule chain is not in system scope!");
431 434 } else {
432   - return ValidationResult.ok();
  435 + return ValidationResult.ok(null);
433 436 }
434 437 }
435 438 }));
... ... @@ -451,7 +454,7 @@ public final class PluginProcessingContext implements PluginContext {
451 454 } else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) {
452 455 return ValidationResult.accessDenied("Plugin is not in system scope!");
453 456 } else {
454   - return ValidationResult.ok();
  457 + return ValidationResult.ok(null);
455 458 }
456 459 }
457 460 }));
... ... @@ -472,7 +475,7 @@ public final class PluginProcessingContext implements PluginContext {
472 475 } else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) {
473 476 return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
474 477 } else {
475   - return ValidationResult.ok();
  478 + return ValidationResult.ok(null);
476 479 }
477 480 }
478 481 }));
... ... @@ -483,7 +486,7 @@ public final class PluginProcessingContext implements PluginContext {
483 486 if (ctx.isCustomerUser()) {
484 487 callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
485 488 } else if (ctx.isSystemAdmin()) {
486   - callback.onSuccess(this, ValidationResult.ok());
  489 + callback.onSuccess(this, ValidationResult.ok(null));
487 490 } else {
488 491 ListenableFuture<Tenant> tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
489 492 Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
... ... @@ -492,7 +495,7 @@ public final class PluginProcessingContext implements PluginContext {
492 495 } else if (!tenant.getId().equals(ctx.getTenantId())) {
493 496 return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
494 497 } else {
495   - return ValidationResult.ok();
  498 + return ValidationResult.ok(null);
496 499 }
497 500 }));
498 501 }
... ...
... ... @@ -18,13 +18,13 @@ package org.thingsboard.server.actors.plugin;
18 18 import akka.actor.ActorRef;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.thingsboard.server.actors.ActorSystemContext;
21   -import org.thingsboard.server.common.data.Device;
22 21 import org.thingsboard.server.common.data.id.DeviceId;
23   -import org.thingsboard.server.common.data.id.EntityId;
  22 +import org.thingsboard.server.common.data.id.PluginId;
24 23 import org.thingsboard.server.common.data.id.TenantId;
25 24 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  25 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  26 +import org.thingsboard.server.common.msg.timeout.TimeoutMsg;
26 27 import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint;
27   -import org.thingsboard.server.common.data.id.PluginId;
28 28 import org.thingsboard.server.dao.asset.AssetService;
29 29 import org.thingsboard.server.dao.attributes.AttributesService;
30 30 import org.thingsboard.server.dao.audit.AuditLogService;
... ... @@ -37,9 +37,6 @@ import org.thingsboard.server.dao.rule.RuleService;
37 37 import org.thingsboard.server.dao.tenant.TenantService;
38 38 import org.thingsboard.server.dao.timeseries.TimeseriesService;
39 39 import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg;
40   -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg;
41   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
42   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
43 40 import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
44 41 import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
45 42 import scala.concurrent.duration.Duration;
... ... @@ -108,8 +105,8 @@ public final class SharedPluginProcessingContext {
108 105
109 106 public void sendRpcRequest(ToDeviceRpcRequest msg) {
110 107 log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg);
111   - ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg);
112   - forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
  108 +// ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg);
  109 +// forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
113 110 }
114 111
115 112 private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
... ...
... ... @@ -20,29 +20,30 @@ import lombok.Data;
20 20
21 21 @Data
22 22 @AllArgsConstructor
23   -public class ValidationResult {
  23 +public class ValidationResult<V> {
24 24
25 25 private final ValidationResultCode resultCode;
26 26 private final String message;
  27 + private final V v;
27 28
28   - public static ValidationResult ok() {
29   - return new ValidationResult(ValidationResultCode.OK, "Ok");
  29 + public static <V> ValidationResult<V> ok(V v) {
  30 + return new ValidationResult<>(ValidationResultCode.OK, "Ok", v);
30 31 }
31 32
32   - public static ValidationResult accessDenied(String message) {
33   - return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message);
  33 + public static <V> ValidationResult<V> accessDenied(String message) {
  34 + return new ValidationResult<>(ValidationResultCode.ACCESS_DENIED, message, null);
34 35 }
35 36
36   - public static ValidationResult entityNotFound(String message) {
37   - return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message);
  37 + public static <V> ValidationResult<V> entityNotFound(String message) {
  38 + return new ValidationResult<>(ValidationResultCode.ENTITY_NOT_FOUND, message, null);
38 39 }
39 40
40   - public static ValidationResult unauthorized(String message) {
41   - return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message);
  41 + public static <V> ValidationResult<V> unauthorized(String message) {
  42 + return new ValidationResult<>(ValidationResultCode.UNAUTHORIZED, message, null);
42 43 }
43 44
44   - public static ValidationResult internalError(String message) {
45   - return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message);
  45 + public static <V> ValidationResult<V> internalError(String message) {
  46 + return new ValidationResult<>(ValidationResultCode.INTERNAL_ERROR, message, null);
46 47 }
47 48
48 49 }
... ...
... ... @@ -24,10 +24,12 @@ import org.thingsboard.server.actors.service.ActorService;
24 24 import org.thingsboard.server.common.data.id.DeviceId;
25 25 import org.thingsboard.server.common.data.id.PluginId;
26 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
27 28 import org.thingsboard.server.common.msg.cluster.ServerAddress;
28 29 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
29 30 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
30   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  31 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
  32 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
31 33 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
32 34 import org.thingsboard.server.extensions.api.plugins.msg.*;
33 35 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
... ... @@ -35,6 +37,7 @@ import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg;
35 37 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
36 38 import org.thingsboard.server.service.cluster.rpc.GrpcSession;
37 39 import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener;
  40 +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
38 41
39 42 import java.io.Serializable;
40 43 import java.util.UUID;
... ... @@ -83,7 +86,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
83 86 @Override
84 87 public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) {
85 88 log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg);
86   - service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
  89 + service.onMsg((DeviceToDeviceActorMsg) deserialize(msg.getData().toByteArray()));
87 90 }
88 91
89 92 @Override
... ... @@ -139,28 +142,20 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
139 142 return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb());
140 143 }
141 144
142   - private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
143   - ClusterAPIProtos.PluginAddress address = msg.getAddress();
144   - TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
145   - PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
146   -
  145 + private static ToDeviceRpcRequestActorMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) {
147 146 TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId()));
148 147 DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId()));
149 148
150 149 ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams());
151   - ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
  150 + ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody);
152 151
153   - return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request);
  152 + return new ToDeviceRpcRequestActorMsg(serverAddress, request);
154 153 }
155 154
156 155 private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) {
157   - ClusterAPIProtos.PluginAddress address = msg.getAddress();
158   - TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId()));
159   - PluginId pluginId = new PluginId(toUUID(address.getPluginId()));
160   -
161 156 RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null;
162 157 FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error);
163   - return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response);
  158 + return new ToPluginRpcResponseDeviceMsg(null, null, response);
164 159 }
165 160
166 161 @SuppressWarnings("unchecked")
... ...
... ... @@ -16,15 +16,20 @@
16 16 package org.thingsboard.server.actors.ruleChain;
17 17
18 18 import akka.actor.ActorRef;
19   -import akka.actor.Cancellable;
  19 +import com.datastax.driver.core.utils.UUIDs;
20 20 import com.google.common.base.Function;
21 21 import org.thingsboard.rule.engine.api.*;
22 22 import org.thingsboard.server.actors.ActorSystemContext;
  23 +import org.thingsboard.server.common.data.id.DeviceId;
  24 +import org.thingsboard.server.common.data.id.EntityId;
23 25 import org.thingsboard.server.common.data.id.RuleNodeId;
24 26 import org.thingsboard.server.common.data.id.TenantId;
  27 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
25 28 import org.thingsboard.server.common.data.rule.RuleNode;
26 29 import org.thingsboard.server.common.msg.TbMsg;
  30 +import org.thingsboard.server.common.msg.TbMsgMetaData;
27 31 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  32 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
28 33 import org.thingsboard.server.dao.alarm.AlarmService;
29 34 import org.thingsboard.server.dao.asset.AssetService;
30 35 import org.thingsboard.server.dao.attributes.AttributesService;
... ... @@ -41,13 +46,13 @@ import scala.concurrent.duration.Duration;
41 46 import java.util.List;
42 47 import java.util.Set;
43 48 import java.util.concurrent.TimeUnit;
  49 +import java.util.function.Consumer;
44 50
45 51 /**
46 52 * Created by ashvayka on 19.03.18.
47 53 */
48 54 class DefaultTbContext implements TbContext {
49 55
50   - private static final Function<? super List<Void>, ? extends Void> LIST_VOID_FUNCTION = v -> null;
51 56 private final ActorSystemContext mainCtx;
52 57 private final RuleNodeCtx nodeCtx;
53 58
... ... @@ -63,8 +68,13 @@ class DefaultTbContext implements TbContext {
63 68
64 69 @Override
65 70 public void tellNext(TbMsg msg, String relationType) {
  71 + tellNext(msg, relationType, null);
  72 + }
  73 +
  74 + @Override
  75 + public void tellNext(TbMsg msg, String relationType, Throwable th) {
66 76 if (nodeCtx.getSelf().isDebugMode()) {
67   - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg);
  77 + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th);
68 78 }
69 79 nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor());
70 80 }
... ... @@ -113,6 +123,16 @@ class DefaultTbContext implements TbContext {
113 123 }
114 124
115 125 @Override
  126 + public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
  127 + return new TbMsg(UUIDs.timeBased(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
  128 + }
  129 +
  130 + @Override
  131 + public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
  132 + return new TbMsg(origMsg.getId(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L);
  133 + }
  134 +
  135 + @Override
116 136 public RuleNodeId getSelfId() {
117 137 return nodeCtx.getSelf().getId();
118 138 }
... ... @@ -143,6 +163,11 @@ class DefaultTbContext implements TbContext {
143 163 }
144 164
145 165 @Override
  166 + public ListeningExecutor getExternalCallExecutor() {
  167 + return mainCtx.getExternalCallExecutorService();
  168 + }
  169 +
  170 + @Override
146 171 public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) {
147 172 return new NashornJsEngine(script, functionName, argNames);
148 173 }
... ... @@ -206,4 +231,28 @@ class DefaultTbContext implements TbContext {
206 231 public MailService getMailService() {
207 232 return mainCtx.getMailService();
208 233 }
  234 +
  235 + @Override
  236 + public RuleEngineRpcService getRpcService() {
  237 + return new RuleEngineRpcService() {
  238 + @Override
  239 + public void sendRpcReply(DeviceId deviceId, int requestId, String body) {
  240 + mainCtx.getDeviceRpcService().sendRpcReplyToDevice(nodeCtx.getTenantId(), deviceId, requestId, body);
  241 + }
  242 +
  243 + @Override
  244 + public void sendRpcRequest(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) {
  245 + ToDeviceRpcRequest request = new ToDeviceRpcRequest(UUIDs.timeBased(), nodeCtx.getTenantId(), src.getDeviceId(),
  246 + src.isOneway(), System.currentTimeMillis() + src.getTimeout(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()));
  247 + mainCtx.getDeviceRpcService().process(request, response -> {
  248 + consumer.accept(RuleEngineDeviceRpcResponse.builder()
  249 + .deviceId(src.getDeviceId())
  250 + .requestId(src.getRequestId())
  251 + .error(response.getError())
  252 + .response(response.getResponse())
  253 + .build());
  254 + });
  255 + }
  256 + };
  257 + }
209 258 }
... ...
... ... @@ -18,6 +18,7 @@ package org.thingsboard.server.actors.ruleChain;
18 18 import akka.actor.OneForOneStrategy;
19 19 import akka.actor.SupervisorStrategy;
20 20 import org.thingsboard.server.actors.ActorSystemContext;
  21 +import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
21 22 import org.thingsboard.server.actors.service.ComponentActor;
22 23 import org.thingsboard.server.actors.service.ContextBasedCreator;
23 24 import org.thingsboard.server.common.data.id.RuleChainId;
... ... @@ -44,9 +45,15 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
44 45 case SERVICE_TO_RULE_ENGINE_MSG:
45 46 processor.onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
46 47 break;
  48 + case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
  49 + processor.onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
  50 + break;
47 51 case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG:
48 52 processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);
49 53 break;
  54 + case RULE_CHAIN_TO_RULE_CHAIN_MSG:
  55 + processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg);
  56 + break;
50 57 default:
51 58 return false;
52 59 }
... ...
... ... @@ -19,7 +19,10 @@ import akka.actor.ActorContext;
19 19 import akka.actor.ActorRef;
20 20 import akka.actor.Props;
21 21 import akka.event.LoggingAdapter;
  22 +import com.datastax.driver.core.utils.UUIDs;
22 23 import org.thingsboard.server.actors.ActorSystemContext;
  24 +import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
  25 +import org.thingsboard.server.actors.device.RuleEngineQueuePutAckMsg;
23 26 import org.thingsboard.server.actors.service.DefaultActorService;
24 27 import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
25 28 import org.thingsboard.server.common.data.EntityType;
... ... @@ -39,6 +42,7 @@ import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
39 42 import org.thingsboard.server.dao.rule.RuleChainService;
40 43
41 44 import java.util.ArrayList;
  45 +import java.util.Collections;
42 46 import java.util.HashMap;
43 47 import java.util.List;
44 48 import java.util.Map;
... ... @@ -50,6 +54,7 @@ import java.util.stream.Collectors;
50 54 */
51 55 public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> {
52 56
  57 + private static final long DEFAULT_CLUSTER_PARTITION = 0L;
53 58 private final ActorRef parent;
54 59 private final ActorRef self;
55 60 private final Map<RuleNodeId, RuleNodeCtx> nodeActors;
... ... @@ -58,6 +63,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
58 63
59 64 private RuleNodeId firstId;
60 65 private RuleNodeCtx firstNode;
  66 + private boolean started;
61 67
62 68 RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext
63 69 , LoggingAdapter logger, ActorRef parent, ActorRef self) {
... ... @@ -71,14 +77,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
71 77
72 78 @Override
73 79 public void start(ActorContext context) throws Exception {
74   - RuleChain ruleChain = service.findRuleChainById(entityId);
75   - List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
76   - // Creating and starting the actors;
  80 + if (!started) {
  81 + RuleChain ruleChain = service.findRuleChainById(entityId);
  82 + List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId);
  83 + // Creating and starting the actors;
  84 + for (RuleNode ruleNode : ruleNodeList) {
  85 + ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
  86 + nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  87 + }
  88 + initRoutes(ruleChain, ruleNodeList);
  89 + reprocess(ruleNodeList);
  90 + started = true;
  91 + } else {
  92 + onUpdate(context);
  93 + }
  94 + }
  95 +
  96 + private void reprocess(List<RuleNode> ruleNodeList) {
77 97 for (RuleNode ruleNode : ruleNodeList) {
78   - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
79   - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
  98 + for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), 0L)) {
  99 + pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg);
  100 + }
  101 + }
  102 + if (firstNode != null) {
  103 + for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), 0L)) {
  104 + pushMsgToNode(firstNode, tbMsg);
  105 + }
80 106 }
81   - initRoutes(ruleChain, ruleNodeList);
82 107 }
83 108
84 109 @Override
... ... @@ -105,6 +130,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
105 130 });
106 131
107 132 initRoutes(ruleChain, ruleNodeList);
  133 + reprocess(ruleNodeList);
108 134 }
109 135
110 136 @Override
... ... @@ -113,6 +139,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
113 139 nodeActors.clear();
114 140 nodeRoutes.clear();
115 141 context.stop(self);
  142 + started = false;
116 143 }
117 144
118 145 @Override
... ... @@ -133,15 +160,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
133 160 // Populating the routes map;
134 161 for (RuleNode ruleNode : ruleNodeList) {
135 162 List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId());
136   - for (EntityRelation relation : relations) {
137   - if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
138   - RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
139   - if (ruleNodeCtx == null) {
140   - throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]");
  163 + if (relations.size() == 0) {
  164 + nodeRoutes.put(ruleNode.getId(), Collections.emptyList());
  165 + } else {
  166 + for (EntityRelation relation : relations) {
  167 + if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
  168 + RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId()));
  169 + if (ruleNodeCtx == null) {
  170 + throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]");
  171 + }
141 172 }
  173 + nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
  174 + .add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
142 175 }
143   - nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>())
144   - .add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType()));
145 176 }
146 177 }
147 178
... ... @@ -152,37 +183,81 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
152 183
153 184 void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) {
154 185 checkActive();
155   - TbMsg tbMsg = envelope.getTbMsg();
156   - //TODO: push to queue and act on ack in async way
157   - pushMsgToNode(firstNode, tbMsg);
  186 + putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg));
  187 + }
  188 +
  189 + void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) {
  190 + checkActive();
  191 + putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> {
  192 + pushMsgToNode(firstNode, msg);
  193 + envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self);
  194 + });
  195 + }
  196 +
  197 + void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
  198 + checkActive();
  199 + if (envelope.isEnqueue()) {
  200 + putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg));
  201 + } else {
  202 + pushMsgToNode(firstNode, envelope.getMsg());
  203 + }
158 204 }
159 205
160 206 void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
161 207 checkActive();
162 208 RuleNodeId originator = envelope.getOriginator();
163 209 String targetRelationType = envelope.getRelationType();
164   - List<RuleNodeRelation> relations = nodeRoutes.get(originator);
165   - if (relations == null) {
166   - return;
167   - }
168   - boolean copy = relations.size() > 1;
169   - for (RuleNodeRelation relation : relations) {
170   - TbMsg msg = envelope.getMsg();
171   - if (copy) {
172   - msg = msg.copy();
  210 + List<RuleNodeRelation> relations = nodeRoutes.get(originator).stream()
  211 + .filter(r -> targetRelationType == null || targetRelationType.equalsIgnoreCase(r.getType()))
  212 + .collect(Collectors.toList());
  213 +
  214 + TbMsg msg = envelope.getMsg();
  215 + int relationsCount = relations.size();
  216 + EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
  217 + if (relationsCount == 0) {
  218 + queue.ack(msg, ackId.getId(), msg.getClusterPartition());
  219 + } else if (relationsCount == 1) {
  220 + for (RuleNodeRelation relation : relations) {
  221 + pushToTarget(msg, relation.getOut());
173 222 }
174   - if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) {
175   - switch (relation.getOut().getEntityType()) {
  223 + } else {
  224 + for (RuleNodeRelation relation : relations) {
  225 + EntityId target = relation.getOut();
  226 + switch (target.getEntityType()) {
176 227 case RULE_NODE:
177   - RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId());
178   - RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId);
179   - pushMsgToNode(targetRuleNode, msg);
  228 + enqueueAndForwardMsgCopyToNode(msg, target);
180 229 break;
181 230 case RULE_CHAIN:
182   -// TODO: implement
  231 + enqueueAndForwardMsgCopyToChain(msg, target);
183 232 break;
184 233 }
185 234 }
  235 + //TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
  236 + queue.ack(msg, ackId.getId(), msg.getClusterPartition());
  237 + }
  238 + }
  239 +
  240 + private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target) {
  241 + RuleChainId targetRCId = new RuleChainId(target.getId());
  242 + TbMsg copyMsg = msg.copy(UUIDs.timeBased(), targetRCId, null, DEFAULT_CLUSTER_PARTITION);
  243 + parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, true), self);
  244 + }
  245 +
  246 + private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target) {
  247 + RuleNodeId targetId = new RuleNodeId(target.getId());
  248 + RuleNodeCtx targetNodeCtx = nodeActors.get(targetId);
  249 + TbMsg copy = msg.copy(UUIDs.timeBased(), entityId, targetId, DEFAULT_CLUSTER_PARTITION);
  250 + putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg));
  251 + }
  252 +
  253 + private void pushToTarget(TbMsg msg, EntityId target) {
  254 + switch (target.getEntityType()) {
  255 + case RULE_NODE:
  256 + pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg);
  257 + break;
  258 + case RULE_CHAIN:
  259 + parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, false), self);
  260 + break;
186 261 }
187 262 }
188 263
... ... @@ -192,4 +267,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
192 267 }
193 268 }
194 269
  270 + private TbMsg enrichWithRuleChainId(TbMsg tbMsg) {
  271 + // We don't put firstNodeId because it may change over time;
  272 + return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData(), tbMsg.getData(), entityId, null, 0L);
  273 + }
195 274 }
... ...
... ... @@ -31,31 +31,29 @@ import org.thingsboard.server.dao.rule.RuleChainService;
31 31 public abstract class RuleChainManagerActor extends ContextAwareActor {
32 32
33 33 protected final RuleChainManager ruleChainManager;
34   - protected final PluginManager pluginManager;
35 34 protected final RuleChainService ruleChainService;
36 35
37 36 public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager, PluginManager pluginManager) {
38 37 super(systemContext);
39 38 this.ruleChainManager = ruleChainManager;
40   - this.pluginManager = pluginManager;
41 39 this.ruleChainService = systemContext.getRuleChainService();
42 40 }
43 41
44 42 protected void initRuleChains() {
45   - pluginManager.init(this.context());
46 43 ruleChainManager.init(this.context());
47 44 }
48 45
49 46 protected ActorRef getEntityActorRef(EntityId entityId) {
50 47 ActorRef target = null;
51 48 switch (entityId.getEntityType()) {
52   - case PLUGIN:
53   - target = pluginManager.getOrCreateActor(this.context(), (PluginId) entityId);
54   - break;
55 49 case RULE_CHAIN:
56 50 target = ruleChainManager.getOrCreateActor(this.context(), (RuleChainId) entityId);
57 51 break;
58 52 }
59 53 return target;
60 54 }
  55 +
  56 + protected void broadcast(Object msg) {
  57 + ruleChainManager.broadcast(msg);
  58 + }
61 59 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.actors.ruleChain;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.rule.engine.api.TbContext;
  20 +import org.thingsboard.server.common.data.id.RuleChainId;
  21 +import org.thingsboard.server.common.msg.MsgType;
  22 +import org.thingsboard.server.common.msg.TbActorMsg;
  23 +import org.thingsboard.server.common.msg.TbMsg;
  24 +
  25 +/**
  26 + * Created by ashvayka on 19.03.18.
  27 + */
  28 +@Data
  29 +public final class RuleChainToRuleChainMsg implements TbActorMsg {
  30 +
  31 + private final RuleChainId target;
  32 + private final RuleChainId source;
  33 + private final TbMsg msg;
  34 + private final boolean enqueue;
  35 +
  36 + @Override
  37 + public MsgType getMsgType() {
  38 + return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;
  39 + }
  40 +}
... ...
... ... @@ -32,4 +32,5 @@ public interface ActorService extends SessionMsgProcessor, WebSocketMsgProcessor
32 32 void onCredentialsUpdate(TenantId tenantId, DeviceId deviceId);
33 33
34 34 void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType);
  35 +
35 36 }
... ...
... ... @@ -39,7 +39,7 @@ import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
39 39 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
40 40 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
41 41 import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg;
42   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  42 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
43 43 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
44 44 import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg;
45 45 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
... ... @@ -156,7 +156,7 @@ public class DefaultActorService implements ActorService {
156 156 }
157 157
158 158 @Override
159   - public void onMsg(ToDeviceActorMsg msg) {
  159 + public void onMsg(DeviceToDeviceActorMsg msg) {
160 160 log.trace("Processing device rpc msg: {}", msg);
161 161 appActor.tell(msg, ActorRef.noSender());
162 162 }
... ...
... ... @@ -22,12 +22,11 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
22 22 import org.thingsboard.server.common.msg.cluster.ServerAddress;
23 23 import org.thingsboard.server.common.msg.core.*;
24 24 import org.thingsboard.server.common.msg.core.SessionCloseMsg;
25   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  25 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
26 26 import org.thingsboard.server.common.msg.session.*;
27 27
28 28 import akka.actor.ActorContext;
29 29 import akka.event.LoggingAdapter;
30   -import org.thingsboard.server.common.msg.session.ctrl.*;
31 30 import org.thingsboard.server.common.msg.session.ex.SessionException;
32 31
33 32 import java.util.HashMap;
... ... @@ -37,7 +36,7 @@ import java.util.Optional;
37 36 class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
38 37
39 38 private boolean firstMsg = true;
40   - private Map<Integer, ToDeviceActorMsg> pendingMap = new HashMap<>();
  39 + private Map<Integer, DeviceToDeviceActorMsg> pendingMap = new HashMap<>();
41 40 private Optional<ServerAddress> currentTargetServer;
42 41 private boolean subscribedToAttributeUpdates;
43 42 private boolean subscribedToRpcCommands;
... ... @@ -53,7 +52,7 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
53 52 toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m));
54 53 firstMsg = false;
55 54 }
56   - ToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
  55 + DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg);
57 56 FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload();
58 57 switch (fromDeviceMsg.getMsgType()) {
59 58 case POST_TELEMETRY_REQUEST:
... ... @@ -86,8 +85,8 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor {
86 85 @Override
87 86 public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) {
88 87 try {
89   - if (msg.getMsgType() != MsgType.SESSION_CLOSE) {
90   - switch (msg.getMsgType()) {
  88 + if (msg.getSessionMsgType() != SessionMsgType.SESSION_CLOSE) {
  89 + switch (msg.getSessionMsgType()) {
91 90 case STATUS_CODE_RESPONSE:
92 91 case GET_ATTRIBUTES_RESPONSE:
93 92 ResponseMsg responseMsg = (ResponseMsg) msg;
... ...
... ... @@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
22 22 import org.thingsboard.server.common.data.id.SessionId;
23 23 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
24 24 import org.thingsboard.server.common.msg.cluster.ServerAddress;
25   -import org.thingsboard.server.common.msg.device.BasicToDeviceActorMsg;
26   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  25 +import org.thingsboard.server.common.msg.device.BasicDeviceToDeviceActorMsg;
  26 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
27 27 import org.thingsboard.server.common.msg.session.*;
28 28 import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
29 29
... ... @@ -37,7 +37,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
37 37
38 38 protected final SessionId sessionId;
39 39 protected SessionContext sessionCtx;
40   - protected ToDeviceActorMsg toDeviceActorMsgPrototype;
  40 + protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype;
41 41
42 42 protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) {
43 43 super(ctx, logger);
... ... @@ -64,29 +64,29 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
64 64
65 65 protected void updateSessionCtx(ToDeviceActorSessionMsg msg, SessionType type) {
66 66 sessionCtx = msg.getSessionMsg().getSessionContext();
67   - toDeviceActorMsgPrototype = new BasicToDeviceActorMsg(msg, type);
  67 + deviceToDeviceActorMsgPrototype = new BasicDeviceToDeviceActorMsg(msg, type);
68 68 }
69 69
70   - protected ToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) {
  70 + protected DeviceToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) {
71 71 AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg();
72   - return new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, adaptorMsg.getMsg());
  72 + return new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, adaptorMsg.getMsg());
73 73 }
74 74
75   - protected Optional<ToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) {
76   - if (toDeviceActorMsgPrototype != null) {
77   - return Optional.of(new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, msg));
  75 + protected Optional<DeviceToDeviceActorMsg> toDeviceMsg(FromDeviceMsg msg) {
  76 + if (deviceToDeviceActorMsgPrototype != null) {
  77 + return Optional.of(new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, msg));
78 78 } else {
79 79 return Optional.empty();
80 80 }
81 81 }
82 82
83   - protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) {
  83 + protected Optional<ServerAddress> forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward) {
84 84 Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
85 85 forwardToAppActor(ctx, toForward, address);
86 86 return address;
87 87 }
88 88
89   - protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
  89 + protected Optional<ServerAddress> forwardToAppActorIfAdressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> oldAddress) {
90 90 Optional<ServerAddress> newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId());
91 91 if (!newAddress.equals(oldAddress)) {
92 92 if (newAddress.isPresent()) {
... ... @@ -99,7 +99,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
99 99 return newAddress;
100 100 }
101 101
102   - protected void forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward, Optional<ServerAddress> address) {
  102 + protected void forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional<ServerAddress> address) {
103 103 if (address.isPresent()) {
104 104 systemContext.getRpcService().tell(address.get(),
105 105 toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer()));
... ... @@ -114,6 +114,6 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP
114 114 }
115 115
116 116 public DeviceId getDeviceId() {
117   - return toDeviceActorMsgPrototype.getDeviceId();
  117 + return deviceToDeviceActorMsgPrototype.getDeviceId();
118 118 }
119 119 }
... ...
... ... @@ -20,7 +20,7 @@ import org.thingsboard.server.actors.shared.SessionTimeoutMsg;
20 20 import org.thingsboard.server.common.data.id.SessionId;
21 21 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
22 22 import org.thingsboard.server.common.msg.cluster.ServerAddress;
23   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  23 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
24 24 import org.thingsboard.server.common.msg.session.*;
25 25 import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg;
26 26 import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg;
... ... @@ -32,7 +32,7 @@ import akka.event.LoggingAdapter;
32 32 import java.util.Optional;
33 33
34 34 class SyncMsgProcessor extends AbstractSessionActorMsgProcessor {
35   - private ToDeviceActorMsg pendingMsg;
  35 + private DeviceToDeviceActorMsg pendingMsg;
36 36 private Optional<ServerAddress> currentTargetServer;
37 37 private boolean pendingResponse;
38 38
... ...
... ... @@ -17,22 +17,32 @@ package org.thingsboard.server.actors.shared;
17 17
18 18 import akka.actor.ActorContext;
19 19 import akka.event.LoggingAdapter;
  20 +import com.google.common.util.concurrent.FutureCallback;
  21 +import com.google.common.util.concurrent.Futures;
20 22 import org.thingsboard.server.actors.ActorSystemContext;
21 23 import org.thingsboard.server.actors.stats.StatsPersistTick;
  24 +import org.thingsboard.server.common.data.id.EntityId;
22 25 import org.thingsboard.server.common.data.id.TenantId;
23 26 import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
  27 +import org.thingsboard.server.common.msg.TbMsg;
24 28 import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
  29 +import org.thingsboard.server.dao.queue.MsgQueue;
25 30
26   -public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgProcessor {
  31 +import javax.annotation.Nullable;
  32 +import java.util.function.Consumer;
  33 +
  34 +public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor {
27 35
28 36 protected final TenantId tenantId;
29 37 protected final T entityId;
  38 + protected final MsgQueue queue;
30 39 protected ComponentLifecycleState state;
31 40
32 41 protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) {
33 42 super(systemContext, logger);
34 43 this.tenantId = tenantId;
35 44 this.entityId = id;
  45 + this.queue = systemContext.getMsgQueue();
36 46 }
37 47
38 48 public abstract void start(ActorContext context) throws Exception;
... ... @@ -75,4 +85,19 @@ public abstract class ComponentMsgProcessor<T> extends AbstractContextAwareMsgPr
75 85 throw new IllegalStateException("Rule chain is not active!");
76 86 }
77 87 }
  88 +
  89 + protected void putToQueue(final TbMsg tbMsg, final Consumer<TbMsg> onSuccess) {
  90 + EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId();
  91 + Futures.addCallback(queue.put(tbMsg, entityId.getId(), 0), new FutureCallback<Void>() {
  92 + @Override
  93 + public void onSuccess(@Nullable Void result) {
  94 + onSuccess.accept(tbMsg);
  95 + }
  96 +
  97 + @Override
  98 + public void onFailure(Throwable t) {
  99 + logger.debug("Failed to push message [{}] to queue due to [{}]", tbMsg, t);
  100 + }
  101 + });
  102 + }
78 103 }
... ...
... ... @@ -16,13 +16,16 @@
16 16 package org.thingsboard.server.actors.tenant;
17 17
18 18 import akka.actor.ActorRef;
  19 +import akka.actor.OneForOneStrategy;
19 20 import akka.actor.Props;
20   -import akka.event.Logging;
21   -import akka.event.LoggingAdapter;
  21 +import akka.actor.SupervisorStrategy;
  22 +import akka.japi.Function;
22 23 import org.thingsboard.server.actors.ActorSystemContext;
23 24 import org.thingsboard.server.actors.device.DeviceActor;
  25 +import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
24 26 import org.thingsboard.server.actors.plugin.PluginTerminationMsg;
25 27 import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
  28 +import org.thingsboard.server.actors.ruleChain.RuleChainToRuleChainMsg;
26 29 import org.thingsboard.server.actors.service.ContextBasedCreator;
27 30 import org.thingsboard.server.actors.service.DefaultActorService;
28 31 import org.thingsboard.server.actors.shared.plugin.TenantPluginManager;
... ... @@ -30,11 +33,13 @@ import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager;
30 33 import org.thingsboard.server.common.data.id.DeviceId;
31 34 import org.thingsboard.server.common.data.id.TenantId;
32 35 import org.thingsboard.server.common.msg.TbActorMsg;
33   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  36 +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
  37 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
34 38 import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
35 39 import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
36 40 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
37 41 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
  42 +import scala.concurrent.duration.Duration;
38 43
39 44 import java.util.HashMap;
40 45 import java.util.Map;
... ... @@ -50,6 +55,12 @@ public class TenantActor extends RuleChainManagerActor {
50 55 this.deviceActors = new HashMap<>();
51 56 }
52 57
  58 +
  59 + @Override
  60 + public SupervisorStrategy supervisorStrategy() {
  61 + return strategy;
  62 + }
  63 +
53 64 @Override
54 65 public void preStart() {
55 66 logger.info("[{}] Starting tenant actor.", tenantId);
... ... @@ -64,63 +75,56 @@ public class TenantActor extends RuleChainManagerActor {
64 75 @Override
65 76 protected boolean process(TbActorMsg msg) {
66 77 switch (msg.getMsgType()) {
  78 + case CLUSTER_EVENT_MSG:
  79 + broadcast(msg);
  80 + break;
67 81 case COMPONENT_LIFE_CYCLE_MSG:
68 82 onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
69 83 break;
70 84 case SERVICE_TO_RULE_ENGINE_MSG:
71 85 onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg);
72 86 break;
  87 + case DEVICE_ACTOR_TO_RULE_ENGINE_MSG:
  88 + onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
  89 + break;
  90 + case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG:
  91 + case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG:
  92 + case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG:
  93 + case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
  94 + case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
  95 + case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
  96 + onToDeviceActorMsg((DeviceAwareMsg) msg);
  97 + break;
  98 + case RULE_CHAIN_TO_RULE_CHAIN_MSG:
  99 + onRuleChainMsg((RuleChainToRuleChainMsg) msg);
  100 + break;
73 101 default:
74 102 return false;
75 103 }
76 104 return true;
77 105 }
78 106
  107 + @Override
  108 + protected void broadcast(Object msg) {
  109 + super.broadcast(msg);
  110 + deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
  111 + }
  112 +
79 113 private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
80 114 ruleChainManager.getRootChainActor().tell(msg, self());
81 115 }
82 116
83   -
84   -// @Override
85   -// public void onReceive(Object msg) throws Exception {
86   -// logger.debug("[{}] Received message: {}", tenantId, msg);
87   -// if (msg instanceof ToDeviceActorMsg) {
88   -// onToDeviceActorMsg((ToDeviceActorMsg) msg);
89   -// } else if (msg instanceof ToPluginActorMsg) {
90   -// onToPluginMsg((ToPluginActorMsg) msg);
91   -// } else if (msg instanceof ToDeviceActorNotificationMsg) {
92   -// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg);
93   -// } else if (msg instanceof ClusterEventMsg) {
94   -// broadcast(msg);
95   -// } else if (msg instanceof ComponentLifecycleMsg) {
96   -// onComponentLifecycleMsg((ComponentLifecycleMsg) msg);
97   -// } else if (msg instanceof PluginTerminationMsg) {
98   -// onPluginTerminated((PluginTerminationMsg) msg);
99   -// } else {
100   -// logger.warning("[{}] Unknown message: {}!", tenantId, msg);
101   -// }
102   -// }
103   -
104   - private void broadcast(Object msg) {
105   - pluginManager.broadcast(msg);
106   - deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
  117 + private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) {
  118 + ruleChainManager.getRootChainActor().tell(msg, self());
107 119 }
108 120
109   - private void onToDeviceActorMsg(ToDeviceActorMsg msg) {
110   - getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
  121 + private void onRuleChainMsg(RuleChainToRuleChainMsg msg) {
  122 + ruleChainManager.getOrCreateActor(context(), msg.getTarget()).tell(msg, self());
111 123 }
112 124
113   - private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) {
114   - getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
115   - }
116 125
117   - private void onToPluginMsg(ToPluginActorMsg msg) {
118   - if (msg.getPluginTenantId().equals(tenantId)) {
119   - ActorRef pluginActor = pluginManager.getOrCreateActor(this.context(), msg.getPluginId());
120   - pluginActor.tell(msg, ActorRef.noSender());
121   - } else {
122   - context().parent().tell(msg, ActorRef.noSender());
123   - }
  126 + private void onToDeviceActorMsg(DeviceAwareMsg msg) {
  127 + getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
124 128 }
125 129
126 130 private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
... ... @@ -132,11 +136,6 @@ public class TenantActor extends RuleChainManagerActor {
132 136 }
133 137 }
134 138
135   - private void onPluginTerminated(PluginTerminationMsg msg) {
136   - pluginManager.remove(msg.getId());
137   - }
138   -
139   -
140 139 private ActorRef getOrCreateDeviceActor(DeviceId deviceId) {
141 140 return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId))
142 141 .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString()));
... ... @@ -158,4 +157,12 @@ public class TenantActor extends RuleChainManagerActor {
158 157 }
159 158 }
160 159
  160 + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() {
  161 + @Override
  162 + public SupervisorStrategy.Directive apply(Throwable t) {
  163 + logger.error(t, "Unknown failure");
  164 + return SupervisorStrategy.resume();
  165 + }
  166 + });
  167 +
161 168 }
... ...
... ... @@ -63,6 +63,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
63 63 import org.thingsboard.server.common.data.exception.ThingsboardException;
64 64 import org.thingsboard.server.service.component.ComponentDiscoveryService;
65 65 import org.thingsboard.server.service.security.model.SecurityUser;
  66 +import org.thingsboard.server.service.state.DeviceStateService;
66 67
67 68 import javax.mail.MessagingException;
68 69 import javax.servlet.http.HttpServletRequest;
... ... @@ -137,6 +138,9 @@ public abstract class BaseController {
137 138 @Autowired
138 139 protected DeviceOfflineService offlineService;
139 140
  141 + @Autowired
  142 + protected DeviceStateService deviceStateService;
  143 +
140 144 @ExceptionHandler(ThingsboardException.class)
141 145 public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) {
142 146 errorResponseHandler.handle(ex, response);
... ... @@ -606,7 +610,7 @@ public abstract class BaseController {
606 610 auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo);
607 611 }
608 612
609   - protected static Exception toException(Throwable error) {
  613 + public static Exception toException(Throwable error) {
610 614 return Exception.class.isInstance(error) ? (Exception) error : new Exception(error);
611 615 }
612 616
... ...
... ... @@ -90,6 +90,11 @@ public class DeviceController extends BaseController {
90 90 savedDevice.getCustomerId(),
91 91 device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
92 92
  93 + if (device.getId() == null) {
  94 + deviceStateService.onDeviceAdded(savedDevice);
  95 + } else {
  96 + deviceStateService.onDeviceUpdated(savedDevice);
  97 + }
93 98 return savedDevice;
94 99 } catch (Exception e) {
95 100 logEntityAction(emptyId(EntityType.DEVICE), device,
... ... @@ -112,6 +117,7 @@ public class DeviceController extends BaseController {
112 117 device.getCustomerId(),
113 118 ActionType.DELETED, null, strDeviceId);
114 119
  120 + deviceStateService.onDeviceDeleted(device);
115 121 } catch (Exception e) {
116 122 logEntityAction(emptyId(EntityType.DEVICE),
117 123 null,
... ... @@ -387,7 +393,7 @@ public class DeviceController extends BaseController {
387 393 @RequestMapping(value = "/device/online", method = RequestMethod.GET)
388 394 @ResponseBody
389 395 public List<Device> getOnlineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType,
390   - @RequestParam("threshold") long threshold) throws ThingsboardException {
  396 + @RequestParam("threshold") long threshold) throws ThingsboardException {
391 397 try {
392 398 TenantId tenantId = getCurrentUser().getTenantId();
393 399 ListenableFuture<List<Device>> offlineDevices = offlineService.findOnlineDevices(tenantId.getId(), contactType, threshold);
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.util.concurrent.FutureCallback;
  21 +import lombok.extern.slf4j.Slf4j;
  22 +import org.springframework.beans.factory.annotation.Autowired;
  23 +import org.springframework.http.HttpStatus;
  24 +import org.springframework.http.ResponseEntity;
  25 +import org.springframework.security.access.prepost.PreAuthorize;
  26 +import org.springframework.util.StringUtils;
  27 +import org.springframework.web.bind.annotation.PathVariable;
  28 +import org.springframework.web.bind.annotation.RequestBody;
  29 +import org.springframework.web.bind.annotation.RequestMapping;
  30 +import org.springframework.web.bind.annotation.RequestMethod;
  31 +import org.springframework.web.bind.annotation.ResponseBody;
  32 +import org.springframework.web.bind.annotation.RestController;
  33 +import org.springframework.web.context.request.async.DeferredResult;
  34 +import org.thingsboard.server.common.data.audit.ActionType;
  35 +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
  36 +import org.thingsboard.server.common.data.exception.ThingsboardException;
  37 +import org.thingsboard.server.common.data.id.DeviceId;
  38 +import org.thingsboard.server.common.data.id.EntityId;
  39 +import org.thingsboard.server.common.data.id.TenantId;
  40 +import org.thingsboard.server.common.data.id.UUIDBased;
  41 +import org.thingsboard.server.common.data.rpc.RpcRequest;
  42 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
  43 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  44 +import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity;
  45 +import org.thingsboard.server.extensions.api.plugins.PluginConstants;
  46 +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
  47 +import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
  48 +import org.thingsboard.server.service.rpc.DeviceRpcService;
  49 +import org.thingsboard.server.service.rpc.LocalRequestMetaData;
  50 +import org.thingsboard.server.service.security.AccessValidator;
  51 +import org.thingsboard.server.service.security.model.SecurityUser;
  52 +
  53 +import javax.annotation.Nullable;
  54 +import javax.annotation.PostConstruct;
  55 +import javax.annotation.PreDestroy;
  56 +import java.io.IOException;
  57 +import java.util.Optional;
  58 +import java.util.UUID;
  59 +import java.util.concurrent.ExecutorService;
  60 +import java.util.concurrent.Executors;
  61 +import java.util.function.Consumer;
  62 +
  63 +/**
  64 + * Created by ashvayka on 22.03.18.
  65 + */
  66 +@RestController
  67 +@RequestMapping(PluginConstants.RPC_URL_PREFIX)
  68 +@Slf4j
  69 +public class RpcController extends BaseController {
  70 +
  71 + public static final int DEFAULT_TIMEOUT = 10000;
  72 + protected final ObjectMapper jsonMapper = new ObjectMapper();
  73 +
  74 + @Autowired
  75 + private DeviceRpcService deviceRpcService;
  76 +
  77 + @Autowired
  78 + private AccessValidator accessValidator;
  79 +
  80 + private ExecutorService executor;
  81 +
  82 + @PostConstruct
  83 + public void initExecutor() {
  84 + executor = Executors.newSingleThreadExecutor();
  85 + }
  86 +
  87 + @PreDestroy
  88 + public void shutdownExecutor() {
  89 + if (executor != null) {
  90 + executor.shutdownNow();
  91 + }
  92 + }
  93 +
  94 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  95 + @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
  96 + @ResponseBody
  97 + public DeferredResult<ResponseEntity> handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
  98 + return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
  99 + }
  100 +
  101 + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
  102 + @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST)
  103 + @ResponseBody
  104 + public DeferredResult<ResponseEntity> handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException {
  105 + return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody);
  106 + }
  107 +
  108 +
  109 + private DeferredResult<ResponseEntity> handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException {
  110 + try {
  111 + JsonNode rpcRequestBody = jsonMapper.readTree(requestBody);
  112 + RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(),
  113 + jsonMapper.writeValueAsString(rpcRequestBody.get("params")));
  114 +
  115 + if (rpcRequestBody.has("timeout")) {
  116 + cmd.setTimeout(rpcRequestBody.get("timeout").asLong());
  117 + }
  118 + SecurityUser currentUser = getCurrentUser();
  119 + TenantId tenantId = currentUser.getTenantId();
  120 + final DeferredResult<ResponseEntity> response = new DeferredResult<>();
  121 + long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT);
  122 + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
  123 + accessValidator.validate(currentUser, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
  124 + @Override
  125 + public void onSuccess(@Nullable DeferredResult<ResponseEntity> result) {
  126 + ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(),
  127 + tenantId,
  128 + deviceId,
  129 + oneWay,
  130 + timeout,
  131 + body
  132 + );
  133 + deviceRpcService.process(rpcRequest, new Consumer<FromDeviceRpcResponse>(){
  134 +
  135 + @Override
  136 + public void accept(FromDeviceRpcResponse fromDeviceRpcResponse) {
  137 + reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse);
  138 + }
  139 + });
  140 + }
  141 +
  142 + @Override
  143 + public void onFailure(Throwable e) {
  144 + ResponseEntity entity;
  145 + if (e instanceof ToErrorResponseEntity) {
  146 + entity = ((ToErrorResponseEntity) e).toErrorResponseEntity();
  147 + } else {
  148 + entity = new ResponseEntity(HttpStatus.UNAUTHORIZED);
  149 + }
  150 + logRpcCall(currentUser, deviceId, body, oneWay, Optional.empty(), e);
  151 + response.setResult(entity);
  152 + }
  153 + }));
  154 + return response;
  155 + } catch (IOException ioe) {
  156 + throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
  157 + }
  158 + }
  159 +
  160 + public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) {
  161 + Optional<RpcError> rpcError = response.getError();
  162 + DeferredResult<ResponseEntity> responseWriter = rpcRequest.getResponseWriter();
  163 + if (rpcError.isPresent()) {
  164 + logRpcCall(rpcRequest, rpcError, null);
  165 + RpcError error = rpcError.get();
  166 + switch (error) {
  167 + case TIMEOUT:
  168 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
  169 + break;
  170 + case NO_ACTIVE_CONNECTION:
  171 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT));
  172 + break;
  173 + default:
  174 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT));
  175 + break;
  176 + }
  177 + } else {
  178 + Optional<String> responseData = response.getResponse();
  179 + if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) {
  180 + String data = responseData.get();
  181 + try {
  182 + logRpcCall(rpcRequest, rpcError, null);
  183 + responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK));
  184 + } catch (IOException e) {
  185 + log.debug("Failed to decode device response: {}", data, e);
  186 + logRpcCall(rpcRequest, rpcError, e);
  187 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE));
  188 + }
  189 + } else {
  190 + logRpcCall(rpcRequest, rpcError, null);
  191 + responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK));
  192 + }
  193 + }
  194 + }
  195 +
  196 + private void logRpcCall(LocalRequestMetaData rpcRequest, Optional<RpcError> rpcError, Throwable e) {
  197 + logRpcCall(rpcRequest.getUser(), rpcRequest.getRequest().getDeviceId(), rpcRequest.getRequest().getBody(), rpcRequest.getRequest().isOneway(), rpcError, null);
  198 + }
  199 +
  200 +
  201 + private void logRpcCall(SecurityUser user, EntityId entityId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Throwable e) {
  202 + String rpcErrorStr = "";
  203 + if (rpcError.isPresent()) {
  204 + rpcErrorStr = "RPC Error: " + rpcError.get().name();
  205 + }
  206 + String method = body.getMethod();
  207 + String params = body.getParams();
  208 +
  209 + auditLogService.logEntityAction(
  210 + user.getTenantId(),
  211 + user.getCustomerId(),
  212 + user.getId(),
  213 + user.getName(),
  214 + (UUIDBased & EntityId) entityId,
  215 + null,
  216 + ActionType.RPC_CALL,
  217 + BaseController.toException(e),
  218 + rpcErrorStr,
  219 + oneWay,
  220 + method,
  221 + params);
  222 + }
  223 +
  224 +
  225 +}
... ...
... ... @@ -237,7 +237,7 @@ public class RuleChainController extends BaseController {
237 237 ScriptEngine engine = null;
238 238 try {
239 239 engine = new NashornJsEngine(script, functionName, argNames);
240   - TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data);
  240 + TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
241 241 switch (scriptType) {
242 242 case "update":
243 243 output = msgToOutput(engine.executeUpdate(inMsg));
... ...
... ... @@ -15,21 +15,34 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
  18 +import lombok.extern.slf4j.Slf4j;
18 19 import org.springframework.beans.factory.annotation.Autowired;
19 20 import org.springframework.http.HttpStatus;
20 21 import org.springframework.security.access.prepost.PreAuthorize;
21   -import org.springframework.web.bind.annotation.*;
  22 +import org.springframework.web.bind.annotation.PathVariable;
  23 +import org.springframework.web.bind.annotation.RequestBody;
  24 +import org.springframework.web.bind.annotation.RequestMapping;
  25 +import org.springframework.web.bind.annotation.RequestMethod;
  26 +import org.springframework.web.bind.annotation.RequestParam;
  27 +import org.springframework.web.bind.annotation.ResponseBody;
  28 +import org.springframework.web.bind.annotation.ResponseStatus;
  29 +import org.springframework.web.bind.annotation.RestController;
22 30 import org.thingsboard.server.common.data.Tenant;
  31 +import org.thingsboard.server.common.data.exception.ThingsboardException;
23 32 import org.thingsboard.server.common.data.id.TenantId;
24 33 import org.thingsboard.server.common.data.page.TextPageData;
25 34 import org.thingsboard.server.common.data.page.TextPageLink;
26 35 import org.thingsboard.server.dao.tenant.TenantService;
27   -import org.thingsboard.server.common.data.exception.ThingsboardException;
  36 +import org.thingsboard.server.service.install.InstallScripts;
28 37
29 38 @RestController
30 39 @RequestMapping("/api")
  40 +@Slf4j
31 41 public class TenantController extends BaseController {
32   -
  42 +
  43 + @Autowired
  44 + private InstallScripts installScripts;
  45 +
33 46 @Autowired
34 47 private TenantService tenantService;
35 48
... ... @@ -49,10 +62,15 @@ public class TenantController extends BaseController {
49 62
50 63 @PreAuthorize("hasAuthority('SYS_ADMIN')")
51 64 @RequestMapping(value = "/tenant", method = RequestMethod.POST)
52   - @ResponseBody
  65 + @ResponseBody
53 66 public Tenant saveTenant(@RequestBody Tenant tenant) throws ThingsboardException {
54 67 try {
55   - return checkNotNull(tenantService.saveTenant(tenant));
  68 + boolean newTenant = tenant.getId() == null;
  69 + tenant = checkNotNull(tenantService.saveTenant(tenant));
  70 + if (newTenant) {
  71 + installScripts.createDefaultRuleChains(tenant.getId());
  72 + }
  73 + return tenant;
56 74 } catch (Exception e) {
57 75 throw handleException(e);
58 76 }
... ... @@ -72,7 +90,7 @@ public class TenantController extends BaseController {
72 90 }
73 91
74 92 @PreAuthorize("hasAuthority('SYS_ADMIN')")
75   - @RequestMapping(value = "/tenants", params = { "limit" }, method = RequestMethod.GET)
  93 + @RequestMapping(value = "/tenants", params = {"limit"}, method = RequestMethod.GET)
76 94 @ResponseBody
77 95 public TextPageData<Tenant> getTenants(@RequestParam int limit,
78 96 @RequestParam(required = false) String textSearch,
... ... @@ -85,5 +103,5 @@ public class TenantController extends BaseController {
85 103 throw handleException(e);
86 104 }
87 105 }
88   -
  106 +
89 107 }
... ...
... ... @@ -23,9 +23,7 @@ import org.springframework.context.ApplicationContext;
23 23 import org.springframework.context.annotation.Profile;
24 24 import org.springframework.stereotype.Service;
25 25 import org.thingsboard.server.service.component.ComponentDiscoveryService;
26   -import org.thingsboard.server.service.install.DatabaseSchemaService;
27   -import org.thingsboard.server.service.install.DatabaseUpgradeService;
28   -import org.thingsboard.server.service.install.SystemDataLoaderService;
  26 +import org.thingsboard.server.service.install.*;
29 27
30 28 import java.nio.file.Paths;
31 29
... ... @@ -40,9 +38,6 @@ public class ThingsboardInstallService {
40 38 @Value("${install.upgrade.from_version:1.2.3}")
41 39 private String upgradeFromVersion;
42 40
43   - @Value("${install.data_dir}")
44   - private String dataDir;
45   -
46 41 @Value("${install.load_demo:false}")
47 42 private Boolean loadDemo;
48 43
... ... @@ -61,6 +56,9 @@ public class ThingsboardInstallService {
61 56 @Autowired
62 57 private SystemDataLoaderService systemDataLoaderService;
63 58
  59 + @Autowired
  60 + private DataUpdateService dataUpdateService;
  61 +
64 62 public void performInstall() {
65 63 try {
66 64 if (isUpgrade) {
... ... @@ -87,6 +85,8 @@ public class ThingsboardInstallService {
87 85
88 86 databaseUpgradeService.upgradeDatabase("1.4.0");
89 87
  88 + dataUpdateService.updateData("1.4.0");
  89 +
90 90 log.info("Updating system data...");
91 91
92 92 systemDataLoaderService.deleteSystemWidgetBundle("charts");
... ... @@ -113,13 +113,6 @@ public class ThingsboardInstallService {
113 113
114 114 log.info("Starting ThingsBoard Installation...");
115 115
116   - if (this.dataDir == null) {
117   - throw new RuntimeException("'install.data_dir' property should specified!");
118   - }
119   - if (!Paths.get(this.dataDir).toFile().isDirectory()) {
120   - throw new RuntimeException("'install.data_dir' property value is not a valid directory!");
121   - }
122   -
123 116 log.info("Installing DataBase schema...");
124 117
125 118 databaseSchemaService.createDatabaseSchema();
... ... @@ -131,8 +124,8 @@ public class ThingsboardInstallService {
131 124 systemDataLoaderService.createSysAdmin();
132 125 systemDataLoaderService.createAdminSettings();
133 126 systemDataLoaderService.loadSystemWidgets();
134   - systemDataLoaderService.loadSystemPlugins();
135   - systemDataLoaderService.loadSystemRules();
  127 +// systemDataLoaderService.loadSystemPlugins();
  128 +// systemDataLoaderService.loadSystemRules();
136 129
137 130 if (loadDemo) {
138 131 log.info("Loading demo data...");
... ...
... ... @@ -19,7 +19,6 @@ import com.google.protobuf.ByteString;
19 19 import io.grpc.Server;
20 20 import io.grpc.ServerBuilder;
21 21 import io.grpc.stub.StreamObserver;
22   -import lombok.Setter;
23 22 import lombok.extern.slf4j.Slf4j;
24 23 import org.springframework.beans.factory.annotation.Autowired;
25 24 import org.springframework.stereotype.Service;
... ... @@ -27,29 +26,24 @@ import org.springframework.util.SerializationUtils;
27 26 import org.thingsboard.server.actors.rpc.RpcBroadcastMsg;
28 27 import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
29 28 import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
30   -import org.thingsboard.server.actors.service.ActorService;
31 29 import org.thingsboard.server.common.data.id.EntityId;
32 30 import org.thingsboard.server.common.msg.cluster.ServerAddress;
33 31 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
34 32 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
35   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  33 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
36 34 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
37 35 import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
38   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
39   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
  36 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
40 37 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
41 38 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
42 39 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
43 40 import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc;
44   -import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
45 41 import org.thingsboard.server.service.cluster.discovery.ServerInstance;
46 42 import org.thingsboard.server.service.cluster.discovery.ServerInstanceService;
47   -import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
  43 +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
48 44
49   -import javax.annotation.PostConstruct;
50 45 import javax.annotation.PreDestroy;
51 46 import java.io.IOException;
52   -import java.util.Set;
53 47 import java.util.UUID;
54 48 import java.util.concurrent.ConcurrentHashMap;
55 49 import java.util.concurrent.ConcurrentMap;
... ... @@ -124,7 +118,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
124 118 }
125 119
126 120 @Override
127   - public void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward) {
  121 + public void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward) {
128 122 ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
129 123 .setToDeviceActorRpcMsg(toProtoMsg(toForward)).build();
130 124 tell(serverAddress, msg);
... ... @@ -138,7 +132,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
138 132 }
139 133
140 134 @Override
141   - public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) {
  135 + public void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward) {
142 136 ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder()
143 137 .setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build();
144 138 tell(serverAddress, msg);
... ... @@ -190,7 +184,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
190 184 }
191 185 }
192 186
193   - private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(ToDeviceActorMsg msg) {
  187 + private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(DeviceToDeviceActorMsg msg) {
194 188 return ClusterAPIProtos.ToDeviceActorRpcMessage.newBuilder().setData(
195 189 ByteString.copyFrom(SerializationUtils.serialize(msg))
196 190 ).build();
... ... @@ -202,15 +196,10 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
202 196 ).build();
203 197 }
204 198
205   - private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) {
  199 + private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestActorMsg msg) {
206 200 ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder();
207 201 ToDeviceRpcRequest request = msg.getMsg();
208 202
209   - builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
210   - .setTenantId(toUid(msg.getPluginTenantId().getId()))
211   - .setPluginId(toUid(msg.getPluginId().getId()))
212   - .build());
213   -
214 203 builder.setDeviceTenantId(toUid(msg.getTenantId()));
215 204 builder.setDeviceId(toUid(msg.getDeviceId()));
216 205
... ... @@ -227,11 +216,6 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI
227 216 ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder();
228 217 FromDeviceRpcResponse request = msg.getResponse();
229 218
230   - builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder()
231   - .setTenantId(toUid(msg.getPluginTenantId().getId()))
232   - .setPluginId(toUid(msg.getPluginId().getId()))
233   - .build());
234   -
235 219 builder.setMsgId(toUid(request.getId()));
236 220 request.getResponse().ifPresent(builder::setResponse);
237 221 request.getError().ifPresent(e -> builder.setError(e.name()));
... ...
... ... @@ -19,12 +19,12 @@ import io.grpc.stub.StreamObserver;
19 19 import org.thingsboard.server.common.msg.cluster.ServerAddress;
20 20 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
21 21 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
22   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  22 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
23 23 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
24   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
25 24 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
26 25 import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
27 26 import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
  27 +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
28 28
29 29 import java.util.UUID;
30 30
... ... @@ -35,13 +35,13 @@ public interface ClusterRpcService {
35 35
36 36 void init(RpcMsgListener listener);
37 37
38   - void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward);
  38 + void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward);
39 39
40 40 void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward);
41 41
42 42 void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward);
43 43
44   - void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward);
  44 + void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward);
45 45
46 46 void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward);
47 47
... ... @@ -50,4 +50,5 @@ public interface ClusterRpcService {
50 50 void broadcast(ToAllNodesMsg msg);
51 51
52 52 void onSessionCreated(UUID msgUid, StreamObserver<ClusterAPIProtos.ToRpcServerMessage> inputStream);
  53 +
53 54 }
... ...
... ... @@ -20,18 +20,16 @@ import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg;
20 20 import org.thingsboard.server.actors.rpc.RpcSessionTellMsg;
21 21 import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
22 22 import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg;
23   -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg;
  23 +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg;
24 24 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
25 25 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg;
26   -import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg;
27   -import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
28 26
29 27 /**
30 28 * @author Andrew Shvayka
31 29 */
32 30 public interface RpcMsgListener {
33 31
34   - void onMsg(ToDeviceActorMsg msg);
  32 + void onMsg(DeviceToDeviceActorMsg msg);
35 33
36 34 void onMsg(ToDeviceActorNotificationMsg msg);
37 35
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.executors;
  17 +
  18 +import org.springframework.beans.factory.annotation.Value;
  19 +import org.springframework.stereotype.Component;
  20 +
  21 +@Component
  22 +public class ExternalCallExecutorService extends AbstractListeningExecutor {
  23 +
  24 + @Value("${actors.rule.external_call_thread_pool_size}")
  25 + private int externalCallExecutorThreadPoolSize;
  26 +
  27 + @Override
  28 + protected int getThreadPollSize() {
  29 + return externalCallExecutorThreadPoolSize;
  30 + }
  31 +
  32 +}
  33 +
... ...
... ... @@ -37,17 +37,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService {
37 37 private static final String CASSANDRA_DIR = "cassandra";
38 38 private static final String SCHEMA_CQL = "schema.cql";
39 39
40   - @Value("${install.data_dir}")
41   - private String dataDir;
42   -
43 40 @Autowired
44 41 private CassandraInstallCluster cluster;
45 42
  43 + @Autowired
  44 + private InstallScripts installScripts;
  45 +
46 46 @Override
47 47 public void createDatabaseSchema() throws Exception {
48 48 log.info("Installing Cassandra DataBase schema...");
49   -
50   - Path schemaFile = Paths.get(this.dataDir, CASSANDRA_DIR, SCHEMA_CQL);
  49 + Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL);
51 50 loadCql(schemaFile);
52 51
53 52 }
... ...
... ... @@ -43,9 +43,6 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
43 43
44 44 private static final String SCHEMA_UPDATE_CQL = "schema_update.cql";
45 45
46   - @Value("${install.data_dir}")
47   - private String dataDir;
48   -
49 46 @Autowired
50 47 private CassandraCluster cluster;
51 48
... ... @@ -55,6 +52,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
55 52 @Autowired
56 53 private DashboardService dashboardService;
57 54
  55 + @Autowired
  56 + private InstallScripts installScripts;
  57 +
58 58 @Override
59 59 public void upgradeDatabase(String fromVersion) throws Exception {
60 60
... ... @@ -91,7 +91,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
91 91 log.info("Relations dumped.");
92 92
93 93 log.info("Updating schema ...");
94   - Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.0", SCHEMA_UPDATE_CQL);
  94 + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.0", SCHEMA_UPDATE_CQL);
95 95 loadCql(schemaUpdateFile);
96 96 log.info("Schema updated.");
97 97
... ... @@ -173,7 +173,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
173 173
174 174
175 175 log.info("Updating schema ...");
176   - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
  176 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_CQL);
177 177 loadCql(schemaUpdateFile);
178 178 log.info("Schema updated.");
179 179
... ... @@ -189,7 +189,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
189 189 case "1.4.0":
190 190
191 191 log.info("Updating schema ...");
192   - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_CQL);
  192 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_CQL);
193 193 loadCql(schemaUpdateFile);
194 194 log.info("Schema updated.");
195 195
... ...
application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java renamed from application/src/test/java/org/thingsboard/server/controller/sql/RuleControllerSqlTest.java
... ... @@ -13,14 +13,10 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.controller.sql;
  16 +package org.thingsboard.server.service.install;
17 17
18   -import org.thingsboard.server.controller.BaseRuleControllerTest;
19   -import org.thingsboard.server.dao.service.DaoSqlTest;
  18 +public interface DataUpdateService {
  19 +
  20 + void updateData(String fromVersion) throws Exception;
20 21
21   -/**
22   - * Created by Valerii Sosliuk on 6/28/2017.
23   - */
24   -@DaoSqlTest
25   -public class RuleControllerSqlTest extends BaseRuleControllerTest {
26 22 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.install;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
  20 +import org.springframework.context.annotation.Profile;
  21 +import org.springframework.stereotype.Service;
  22 +import org.thingsboard.server.common.data.Tenant;
  23 +import org.thingsboard.server.common.data.id.IdBased;
  24 +import org.thingsboard.server.common.data.page.TextPageLink;
  25 +import org.thingsboard.server.common.data.rule.RuleChain;
  26 +import org.thingsboard.server.dao.rule.RuleChainService;
  27 +import org.thingsboard.server.dao.tenant.TenantService;
  28 +
  29 +import java.util.List;
  30 +import java.util.UUID;
  31 +
  32 +@Service
  33 +@Profile("install")
  34 +@Slf4j
  35 +public class DefaultDataUpdateService implements DataUpdateService {
  36 +
  37 + @Autowired
  38 + private TenantService tenantService;
  39 +
  40 + @Autowired
  41 + private RuleChainService ruleChainService;
  42 +
  43 + @Autowired
  44 + private InstallScripts installScripts;
  45 +
  46 + @Override
  47 + public void updateData(String fromVersion) throws Exception {
  48 + switch (fromVersion) {
  49 + case "1.4.0":
  50 + log.info("Updating data from version 1.4.0 to 1.5.0 ...");
  51 + tenantsDefaultRuleChainUpdater.updateEntities(null);
  52 + break;
  53 + default:
  54 + throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
  55 + }
  56 + }
  57 +
  58 + private PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater =
  59 + new PaginatedUpdater<String, Tenant>() {
  60 +
  61 + @Override
  62 + protected List<Tenant> findEntities(String region, TextPageLink pageLink) {
  63 + return tenantService.findTenants(pageLink).getData();
  64 + }
  65 +
  66 + @Override
  67 + protected void updateEntity(Tenant tenant) {
  68 + try {
  69 + RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId());
  70 + if (ruleChain == null) {
  71 + installScripts.createDefaultRuleChains(tenant.getId());
  72 + }
  73 + } catch (Exception e) {
  74 + log.error("Unable to update Tenant", e);
  75 + }
  76 + }
  77 + };
  78 +
  79 + public abstract class PaginatedUpdater<I, D extends IdBased<?>> {
  80 +
  81 + private static final int DEFAULT_LIMIT = 100;
  82 +
  83 + public void updateEntities(I id) {
  84 + TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT);
  85 + boolean hasNext = true;
  86 + while (hasNext) {
  87 + List<D> entities = findEntities(id, pageLink);
  88 + for (D entity : entities) {
  89 + updateEntity(entity);
  90 + }
  91 + hasNext = entities.size() == pageLink.getLimit();
  92 + if (hasNext) {
  93 + int index = entities.size() - 1;
  94 + UUID idOffset = entities.get(index).getUuidId();
  95 + pageLink.setIdOffset(idOffset);
  96 + }
  97 + }
  98 + }
  99 +
  100 + protected abstract List<D> findEntities(I id, TextPageLink pageLink);
  101 +
  102 + protected abstract void updateEntity(D entity);
  103 +
  104 + }
  105 +
  106 +}
\ No newline at end of file
... ...
... ... @@ -60,21 +60,12 @@ import java.nio.file.Paths;
60 60 @Slf4j
61 61 public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
62 62
63   - private static final String JSON_DIR = "json";
64   - private static final String SYSTEM_DIR = "system";
65   - private static final String DEMO_DIR = "demo";
66   - private static final String WIDGET_BUNDLES_DIR = "widget_bundles";
67   - private static final String PLUGINS_DIR = "plugins";
68   - private static final String RULES_DIR = "rules";
69   - private static final String DASHBOARDS_DIR = "dashboards";
70   -
71 63 private static final ObjectMapper objectMapper = new ObjectMapper();
72   - public static final String JSON_EXT = ".json";
73 64 public static final String CUSTOMER_CRED = "customer";
74 65 public static final String DEFAULT_DEVICE_TYPE = "default";
75 66
76   - @Value("${install.data_dir}")
77   - private String dataDir;
  67 + @Autowired
  68 + private InstallScripts installScripts;
78 69
79 70 @Autowired
80 71 private BCryptPasswordEncoder passwordEncoder;
... ... @@ -89,15 +80,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
89 80 private WidgetsBundleService widgetsBundleService;
90 81
91 82 @Autowired
92   - private WidgetTypeService widgetTypeService;
93   -
94   - @Autowired
95   - private PluginService pluginService;
96   -
97   - @Autowired
98   - private RuleService ruleService;
99   -
100   - @Autowired
101 83 private TenantService tenantService;
102 84
103 85 @Autowired
... ... @@ -109,9 +91,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
109 91 @Autowired
110 92 private DeviceCredentialsService deviceCredentialsService;
111 93
112   - @Autowired
113   - private DashboardService dashboardService;
114   -
115 94 @Bean
116 95 protected BCryptPasswordEncoder passwordEncoder() {
117 96 return new BCryptPasswordEncoder();
... ... @@ -147,55 +126,12 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
147 126 }
148 127
149 128 @Override
150   - public void loadSystemWidgets() throws Exception {
151   - Path widgetBundlesDir = Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
152   - try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
153   - dirStream.forEach(
154   - path -> {
155   - try {
156   - JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile());
157   - JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
158   - WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class);
159   - WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
160   - JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
161   - widgetTypesArrayJson.forEach(
162   - widgetTypeJson -> {
163   - try {
164   - WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class);
165   - widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
166   - widgetTypeService.saveWidgetType(widgetType);
167   - } catch (Exception e) {
168   - log.error("Unable to load widget type from json: [{}]", path.toString());
169   - throw new RuntimeException("Unable to load widget type from json", e);
170   - }
171   - }
172   - );
173   - } catch (Exception e) {
174   - log.error("Unable to load widgets bundle from json: [{}]", path.toString());
175   - throw new RuntimeException("Unable to load widgets bundle from json", e);
176   - }
177   - }
178   - );
179   - }
180   - }
181   -
182   - @Override
183   - public void loadSystemPlugins() throws Exception {
184   - loadPlugins(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, PLUGINS_DIR), null);
185   - }
186   -
187   -
188   - @Override
189   - public void loadSystemRules() throws Exception {
190   -// loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null);
191   - }
192   -
193   - @Override
194 129 public void loadDemoData() throws Exception {
195 130 Tenant demoTenant = new Tenant();
196 131 demoTenant.setRegion("Global");
197 132 demoTenant.setTitle("Tenant");
198 133 demoTenant = tenantService.saveTenant(demoTenant);
  134 + installScripts.createDefaultRuleChains(demoTenant.getId());
199 135 createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant");
200 136
201 137 Customer customerA = new Customer();
... ... @@ -227,9 +163,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
227 163 createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " +
228 164 "Raspberry Pi GPIO control sample application");
229 165
230   - loadPlugins(Paths.get(dataDir, JSON_DIR, DEMO_DIR, PLUGINS_DIR), demoTenant.getId());
231   -// loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId());
232   - loadDashboards(Paths.get(dataDir, JSON_DIR, DEMO_DIR, DASHBOARDS_DIR), demoTenant.getId(), null);
  166 + installScripts.loadDashboards(demoTenant.getId(), null);
233 167 }
234 168
235 169 @Override
... ... @@ -240,6 +174,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
240 174 }
241 175 }
242 176
  177 + @Override
  178 + public void loadSystemWidgets() throws Exception {
  179 + installScripts.loadSystemWidgets();
  180 + }
  181 +
243 182 private User createUser(Authority authority,
244 183 TenantId tenantId,
245 184 CustomerId customerId,
... ... @@ -282,72 +221,4 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
282 221 return device;
283 222 }
284 223
285   - private void loadPlugins(Path pluginsDir, TenantId tenantId) throws Exception{
286   - try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(pluginsDir, path -> path.toString().endsWith(JSON_EXT))) {
287   - dirStream.forEach(
288   - path -> {
289   - try {
290   - JsonNode pluginJson = objectMapper.readTree(path.toFile());
291   - PluginMetaData plugin = objectMapper.treeToValue(pluginJson, PluginMetaData.class);
292   - plugin.setTenantId(tenantId);
293   - if (plugin.getState() == ComponentLifecycleState.ACTIVE) {
294   - plugin.setState(ComponentLifecycleState.SUSPENDED);
295   - PluginMetaData savedPlugin = pluginService.savePlugin(plugin);
296   - pluginService.activatePluginById(savedPlugin.getId());
297   - } else {
298   - pluginService.savePlugin(plugin);
299   - }
300   - } catch (Exception e) {
301   - log.error("Unable to load plugin from json: [{}]", path.toString());
302   - throw new RuntimeException("Unable to load plugin from json", e);
303   - }
304   - }
305   - );
306   - }
307   - }
308   -
309   - private void loadRules(Path rulesDir, TenantId tenantId) throws Exception {
310   - try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(rulesDir, path -> path.toString().endsWith(JSON_EXT))) {
311   - dirStream.forEach(
312   - path -> {
313   - try {
314   - JsonNode ruleJson = objectMapper.readTree(path.toFile());
315   - RuleMetaData rule = objectMapper.treeToValue(ruleJson, RuleMetaData.class);
316   - rule.setTenantId(tenantId);
317   - if (rule.getState() == ComponentLifecycleState.ACTIVE) {
318   - rule.setState(ComponentLifecycleState.SUSPENDED);
319   - RuleMetaData savedRule = ruleService.saveRule(rule);
320   - ruleService.activateRuleById(savedRule.getId());
321   - } else {
322   - ruleService.saveRule(rule);
323   - }
324   - } catch (Exception e) {
325   - log.error("Unable to load rule from json: [{}]", path.toString());
326   - throw new RuntimeException("Unable to load rule from json", e);
327   - }
328   - }
329   - );
330   - }
331   - }
332   -
333   - private void loadDashboards(Path dashboardsDir, TenantId tenantId, CustomerId customerId) throws Exception {
334   - try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
335   - dirStream.forEach(
336   - path -> {
337   - try {
338   - JsonNode dashboardJson = objectMapper.readTree(path.toFile());
339   - Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
340   - dashboard.setTenantId(tenantId);
341   - Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
342   - if (customerId != null && !customerId.isNullUid()) {
343   - dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
344   - }
345   - } catch (Exception e) {
346   - log.error("Unable to load dashboard from json: [{}]", path.toString());
347   - throw new RuntimeException("Unable to load dashboard from json", e);
348   - }
349   - }
350   - );
351   - }
352   - }
353 224 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.install;
  17 +
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import lombok.Getter;
  20 +import lombok.extern.slf4j.Slf4j;
  21 +import org.springframework.beans.factory.annotation.Autowired;
  22 +import org.springframework.beans.factory.annotation.Value;
  23 +import org.springframework.stereotype.Component;
  24 +import org.springframework.util.StringUtils;
  25 +import org.thingsboard.server.common.data.Dashboard;
  26 +import org.thingsboard.server.common.data.id.CustomerId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
  28 +import org.thingsboard.server.common.data.rule.RuleChain;
  29 +import org.thingsboard.server.common.data.rule.RuleChainMetaData;
  30 +import org.thingsboard.server.common.data.widget.WidgetType;
  31 +import org.thingsboard.server.common.data.widget.WidgetsBundle;
  32 +import org.thingsboard.server.dao.dashboard.DashboardService;
  33 +import org.thingsboard.server.dao.rule.RuleChainService;
  34 +import org.thingsboard.server.dao.widget.WidgetTypeService;
  35 +import org.thingsboard.server.dao.widget.WidgetsBundleService;
  36 +
  37 +import java.io.IOException;
  38 +import java.nio.file.DirectoryStream;
  39 +import java.nio.file.Files;
  40 +import java.nio.file.Path;
  41 +import java.nio.file.Paths;
  42 +
  43 +import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper;
  44 +
  45 +/**
  46 + * Created by ashvayka on 18.04.18.
  47 + */
  48 +@Component
  49 +@Slf4j
  50 +public class InstallScripts {
  51 +
  52 + public static final String APP_DIR = "application";
  53 + public static final String SRC_DIR = "src";
  54 + public static final String MAIN_DIR = "main";
  55 + public static final String DATA_DIR = "data";
  56 + public static final String JSON_DIR = "json";
  57 + public static final String SYSTEM_DIR = "system";
  58 + public static final String TENANT_DIR = "tenant";
  59 + public static final String DEMO_DIR = "demo";
  60 + public static final String RULE_CHAINS_DIR = "rule_chains";
  61 + public static final String WIDGET_BUNDLES_DIR = "widget_bundles";
  62 + public static final String DASHBOARDS_DIR = "dashboards";
  63 +
  64 + public static final String JSON_EXT = ".json";
  65 +
  66 + @Value("${install.data_dir:}")
  67 + private String dataDir;
  68 +
  69 + @Autowired
  70 + private RuleChainService ruleChainService;
  71 +
  72 + @Autowired
  73 + private DashboardService dashboardService;
  74 +
  75 + @Autowired
  76 + private WidgetTypeService widgetTypeService;
  77 +
  78 + @Autowired
  79 + private WidgetsBundleService widgetsBundleService;
  80 +
  81 + public Path getTenantRuleChainsDir() {
  82 + return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR);
  83 + }
  84 +
  85 + public String getDataDir() {
  86 + if (!StringUtils.isEmpty(dataDir)) {
  87 + if (!Paths.get(this.dataDir).toFile().isDirectory()) {
  88 + throw new RuntimeException("'install.data_dir' property value is not a valid directory!");
  89 + }
  90 + return dataDir;
  91 + } else {
  92 + String workDir = System.getProperty("user.dir");
  93 + if (workDir.endsWith("application")) {
  94 + return Paths.get(workDir, SRC_DIR, MAIN_DIR, DATA_DIR).toString();
  95 + } else {
  96 + Path dataDirPath = Paths.get(workDir, APP_DIR, SRC_DIR, MAIN_DIR, DATA_DIR);
  97 + if (Files.exists(dataDirPath)) {
  98 + return dataDirPath.toString();
  99 + } else {
  100 + throw new RuntimeException("Not valid working directory: " + workDir + ". Please use either root project directory, application module directory or specify valid \"install.data_dir\" ENV variable to avoid automatic data directory lookup!");
  101 + }
  102 + }
  103 + }
  104 + }
  105 +
  106 + public void createDefaultRuleChains(TenantId tenantId) throws IOException {
  107 + Path tenantChainsDir = getTenantRuleChainsDir();
  108 + try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(tenantChainsDir, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) {
  109 + dirStream.forEach(
  110 + path -> {
  111 + try {
  112 + JsonNode ruleChainJson = objectMapper.readTree(path.toFile());
  113 + RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class);
  114 + RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class);
  115 +
  116 + ruleChain.setTenantId(tenantId);
  117 + ruleChain = ruleChainService.saveRuleChain(ruleChain);
  118 +
  119 + ruleChainMetaData.setRuleChainId(ruleChain.getId());
  120 + ruleChainService.saveRuleChainMetaData(ruleChainMetaData);
  121 + } catch (Exception e) {
  122 + log.error("Unable to load rule chain from json: [{}]", path.toString());
  123 + throw new RuntimeException("Unable to load rule chain from json", e);
  124 + }
  125 + }
  126 + );
  127 + }
  128 + }
  129 +
  130 + public void loadSystemWidgets() throws Exception {
  131 + Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR);
  132 + try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) {
  133 + dirStream.forEach(
  134 + path -> {
  135 + try {
  136 + JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile());
  137 + JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle");
  138 + WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class);
  139 + WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle);
  140 + JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes");
  141 + widgetTypesArrayJson.forEach(
  142 + widgetTypeJson -> {
  143 + try {
  144 + WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class);
  145 + widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
  146 + widgetTypeService.saveWidgetType(widgetType);
  147 + } catch (Exception e) {
  148 + log.error("Unable to load widget type from json: [{}]", path.toString());
  149 + throw new RuntimeException("Unable to load widget type from json", e);
  150 + }
  151 + }
  152 + );
  153 + } catch (Exception e) {
  154 + log.error("Unable to load widgets bundle from json: [{}]", path.toString());
  155 + throw new RuntimeException("Unable to load widgets bundle from json", e);
  156 + }
  157 + }
  158 + );
  159 + }
  160 + }
  161 +
  162 + public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception {
  163 + Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR);
  164 + try (DirectoryStream<Path> dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) {
  165 + dirStream.forEach(
  166 + path -> {
  167 + try {
  168 + JsonNode dashboardJson = objectMapper.readTree(path.toFile());
  169 + Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class);
  170 + dashboard.setTenantId(tenantId);
  171 + Dashboard savedDashboard = dashboardService.saveDashboard(dashboard);
  172 + if (customerId != null && !customerId.isNullUid()) {
  173 + dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId);
  174 + }
  175 + } catch (Exception e) {
  176 + log.error("Unable to load dashboard from json: [{}]", path.toString());
  177 + throw new RuntimeException("Unable to load dashboard from json", e);
  178 + }
  179 + }
  180 + );
  181 + }
  182 + }
  183 +
  184 +
  185 +}
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.service.install;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.beans.factory.annotation.Autowired;
19 20 import org.springframework.beans.factory.annotation.Value;
20 21 import org.springframework.context.annotation.Profile;
21 22 import org.springframework.stereotype.Service;
... ... @@ -38,9 +39,6 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
38 39 private static final String SQL_DIR = "sql";
39 40 private static final String SCHEMA_SQL = "schema.sql";
40 41
41   - @Value("${install.data_dir}")
42   - private String dataDir;
43   -
44 42 @Value("${spring.datasource.url}")
45 43 private String dbUrl;
46 44
... ... @@ -50,12 +48,15 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService {
50 48 @Value("${spring.datasource.password}")
51 49 private String dbPassword;
52 50
  51 + @Autowired
  52 + private InstallScripts installScripts;
  53 +
53 54 @Override
54 55 public void createDatabaseSchema() throws Exception {
55 56
56 57 log.info("Installing SQL DataBase schema...");
57 58
58   - Path schemaFile = Paths.get(this.dataDir, SQL_DIR, SCHEMA_SQL);
  59 + Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL);
59 60 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
60 61 String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
61 62 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema
... ...
... ... @@ -44,9 +44,6 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
44 44
45 45 private static final String SCHEMA_UPDATE_SQL = "schema_update.sql";
46 46
47   - @Value("${install.data_dir}")
48   - private String dataDir;
49   -
50 47 @Value("${spring.datasource.url}")
51 48 private String dbUrl;
52 49
... ... @@ -59,12 +56,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
59 56 @Autowired
60 57 private DashboardService dashboardService;
61 58
  59 + @Autowired
  60 + private InstallScripts installScripts;
  61 +
62 62 @Override
63 63 public void upgradeDatabase(String fromVersion) throws Exception {
64 64 switch (fromVersion) {
65 65 case "1.3.0":
66 66 log.info("Updating schema ...");
67   - Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
  67 + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL);
68 68 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
69 69 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
70 70 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
... ... @@ -82,7 +82,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
82 82 log.info("Dashboards dumped.");
83 83
84 84 log.info("Updating schema ...");
85   - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
  85 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_SQL);
86 86 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
87 87 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
88 88 log.info("Schema updated.");
... ... @@ -100,7 +100,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
100 100 case "1.4.0":
101 101 try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
102 102 log.info("Updating schema ...");
103   - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_SQL);
  103 + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_SQL);
104 104 String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8"));
105 105 conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
106 106 log.info("Schema updated.");
... ...
... ... @@ -23,10 +23,6 @@ public interface SystemDataLoaderService {
23 23
24 24 void loadSystemWidgets() throws Exception;
25 25
26   - void loadSystemPlugins() throws Exception;
27   -
28   - void loadSystemRules() throws Exception;
29   -
30 26 void loadDemoData() throws Exception;
31 27
32 28 void deleteSystemWidgetBundle(String bundleAlias) throws Exception;
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.rpc;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.springframework.beans.factory.annotation.Autowired;
  21 +import org.springframework.http.HttpStatus;
  22 +import org.springframework.http.ResponseEntity;
  23 +import org.springframework.stereotype.Service;
  24 +import org.springframework.util.StringUtils;
  25 +import org.springframework.web.context.request.async.DeferredResult;
  26 +import org.thingsboard.server.actors.service.ActorService;
  27 +import org.thingsboard.server.common.data.audit.ActionType;
  28 +import org.thingsboard.server.common.data.id.DeviceId;
  29 +import org.thingsboard.server.common.data.id.EntityId;
  30 +import org.thingsboard.server.common.data.id.TenantId;
  31 +import org.thingsboard.server.common.data.id.UUIDBased;
  32 +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
  33 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  34 +import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
  35 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  36 +import org.thingsboard.server.controller.BaseController;
  37 +import org.thingsboard.server.dao.audit.AuditLogService;
  38 +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
  39 +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
  40 +import org.thingsboard.server.extensions.api.plugins.msg.RpcError;
  41 +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService;
  42 +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService;
  43 +import org.thingsboard.server.service.security.model.SecurityUser;
  44 +
  45 +import javax.annotation.PostConstruct;
  46 +import javax.annotation.PreDestroy;
  47 +import java.io.IOException;
  48 +import java.util.Optional;
  49 +import java.util.UUID;
  50 +import java.util.concurrent.ConcurrentHashMap;
  51 +import java.util.concurrent.ConcurrentMap;
  52 +import java.util.concurrent.Executors;
  53 +import java.util.concurrent.ScheduledExecutorService;
  54 +import java.util.concurrent.TimeUnit;
  55 +import java.util.function.BiConsumer;
  56 +import java.util.function.Consumer;
  57 +
  58 +/**
  59 + * Created by ashvayka on 27.03.18.
  60 + */
  61 +@Service
  62 +@Slf4j
  63 +public class DefaultDeviceRpcService implements DeviceRpcService {
  64 +
  65 + @Autowired
  66 + private ClusterRoutingService routingService;
  67 +
  68 + @Autowired
  69 + private ClusterRpcService rpcService;
  70 +
  71 + @Autowired
  72 + private ActorService actorService;
  73 +
  74 + @Autowired
  75 + private AuditLogService auditLogService;
  76 +
  77 + private ScheduledExecutorService rpcCallBackExecutor;
  78 +
  79 + private final ConcurrentMap<UUID, Consumer<FromDeviceRpcResponse>> localRpcRequests = new ConcurrentHashMap<>();
  80 +
  81 +
  82 + @PostConstruct
  83 + public void initExecutor() {
  84 + rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor();
  85 + }
  86 +
  87 + @PreDestroy
  88 + public void shutdownExecutor() {
  89 + if (rpcCallBackExecutor != null) {
  90 + rpcCallBackExecutor.shutdownNow();
  91 + }
  92 + }
  93 +
  94 + @Override
  95 + public void process(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
  96 + log.trace("[{}] Processing local rpc call for device [{}]", request.getTenantId(), request.getDeviceId());
  97 + sendRpcRequest(request);
  98 + UUID requestId = request.getId();
  99 + localRpcRequests.put(requestId, responseConsumer);
  100 + long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis());
  101 + log.error("[{}] processing the request: [{}]", this.hashCode(), requestId);
  102 + rpcCallBackExecutor.schedule(() -> {
  103 + log.error("[{}] timeout the request: [{}]", this.hashCode(), requestId);
  104 + Consumer<FromDeviceRpcResponse> consumer = localRpcRequests.remove(requestId);
  105 + if (consumer != null) {
  106 + consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT));
  107 + }
  108 + }, timeout, TimeUnit.MILLISECONDS);
  109 + }
  110 +
  111 + @Override
  112 + public void process(ToDeviceRpcRequest request, ServerAddress originator) {
  113 +// if (pluginServerAddress.isPresent()) {
  114 +// systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg);
  115 +// logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
  116 +// } else {
  117 +// context.parent().tell(responsePluginMsg, ActorRef.noSender());
  118 +// logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId());
  119 +// }
  120 + }
  121 +
  122 + @Override
  123 + public void process(FromDeviceRpcResponse response) {
  124 + log.error("[{}] response the request: [{}]", this.hashCode(), response.getId());
  125 + //TODO: send to another server if needed.
  126 + UUID requestId = response.getId();
  127 + Consumer<FromDeviceRpcResponse> consumer = localRpcRequests.remove(requestId);
  128 + if (consumer != null) {
  129 + consumer.accept(response);
  130 + } else {
  131 + log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
  132 + }
  133 + }
  134 +
  135 + @Override
  136 + public void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) {
  137 + ToServerRpcResponseActorMsg rpcMsg = new ToServerRpcResponseActorMsg(tenantId, deviceId, new ToServerRpcResponseMsg(requestId, body));
  138 + forward(deviceId, rpcMsg, rpcService::tell);
  139 + }
  140 +
  141 + private void sendRpcRequest(ToDeviceRpcRequest msg) {
  142 + log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg);
  143 + ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(msg);
  144 + forward(msg.getDeviceId(), rpcMsg, rpcService::tell);
  145 + }
  146 +
  147 + private <T extends ToDeviceActorNotificationMsg> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) {
  148 + Optional<ServerAddress> instance = routingService.resolveById(deviceId);
  149 + if (instance.isPresent()) {
  150 + log.trace("[{}] Forwarding msg {} to remote device actor!", msg.getTenantId(), msg);
  151 + rpcFunction.accept(instance.get(), msg);
  152 + } else {
  153 + log.trace("[{}] Forwarding msg {} to local device actor!", msg.getTenantId(), msg);
  154 + actorService.onMsg(msg);
  155 + }
  156 + }
  157 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.rpc;
  17 +
  18 +import org.thingsboard.server.common.data.id.DeviceId;
  19 +import org.thingsboard.server.common.data.id.TenantId;
  20 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  21 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  22 +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse;
  23 +
  24 +import java.util.function.Consumer;
  25 +
  26 +/**
  27 + * Created by ashvayka on 16.04.18.
  28 + */
  29 +public interface DeviceRpcService {
  30 +
  31 + void process(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
  32 +
  33 + void process(ToDeviceRpcRequest request, ServerAddress originator);
  34 +
  35 + void process(FromDeviceRpcResponse response);
  36 +
  37 + void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body);
  38 +
  39 +}
... ...
application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java renamed from extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/LocalRequestMetaData.java
... ... @@ -13,18 +13,20 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.extensions.core.plugin.rpc;
  16 +package org.thingsboard.server.service.rpc;
17 17
18 18 import lombok.Data;
19 19 import org.springframework.http.ResponseEntity;
20 20 import org.springframework.web.context.request.async.DeferredResult;
21   -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest;
  21 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
  22 +import org.thingsboard.server.service.security.model.SecurityUser;
22 23
23 24 /**
24   - * @author Andrew Shvayka
  25 + * Created by ashvayka on 16.04.18.
25 26 */
26 27 @Data
27 28 public class LocalRequestMetaData {
28 29 private final ToDeviceRpcRequest request;
  30 + private final SecurityUser user;
29 31 private final DeferredResult<ResponseEntity> responseWriter;
30 32 }
... ...
application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java renamed from extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java
... ... @@ -13,36 +13,33 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -package org.thingsboard.server.extensions.api.plugins.msg;
  16 +package org.thingsboard.server.service.rpc;
17 17
18 18 import lombok.Getter;
19 19 import lombok.RequiredArgsConstructor;
20 20 import lombok.ToString;
21 21 import org.thingsboard.server.common.data.id.DeviceId;
22   -import org.thingsboard.server.common.data.id.PluginId;
23 22 import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.common.msg.MsgType;
24 24 import org.thingsboard.server.common.msg.cluster.ServerAddress;
  25 +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
25 26 import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
26 27
27 28 import java.util.Optional;
28 29
29 30 /**
30   - * @author Andrew Shvayka
  31 + * Created by ashvayka on 16.04.18.
31 32 */
32 33 @ToString
33 34 @RequiredArgsConstructor
34   -public class ToDeviceRpcRequestPluginMsg implements ToDeviceActorNotificationMsg {
  35 +public class ToDeviceRpcRequestActorMsg implements ToDeviceActorNotificationMsg {
35 36
36 37 private final ServerAddress serverAddress;
37 38 @Getter
38   - private final PluginId pluginId;
39   - @Getter
40   - private final TenantId pluginTenantId;
41   - @Getter
42 39 private final ToDeviceRpcRequest msg;
43 40
44   - public ToDeviceRpcRequestPluginMsg(PluginId pluginId, TenantId pluginTenantId, ToDeviceRpcRequest msg) {
45   - this(null, pluginId, pluginTenantId, msg);
  41 + public ToDeviceRpcRequestActorMsg(ToDeviceRpcRequest msg) {
  42 + this(null, msg);
46 43 }
47 44
48 45 public Optional<ServerAddress> getServerAddress() {
... ... @@ -58,5 +55,9 @@ public class ToDeviceRpcRequestPluginMsg implements ToDeviceActorNotificationMsg
58 55 public TenantId getTenantId() {
59 56 return msg.getTenantId();
60 57 }
61   -}
62 58
  59 + @Override
  60 + public MsgType getMsgType() {
  61 + return MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG;
  62 + }
  63 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.rpc;
  17 +
  18 +import lombok.Getter;
  19 +import lombok.RequiredArgsConstructor;
  20 +import lombok.ToString;
  21 +import org.thingsboard.server.common.data.id.DeviceId;
  22 +import org.thingsboard.server.common.data.id.TenantId;
  23 +import org.thingsboard.server.common.msg.MsgType;
  24 +import org.thingsboard.server.common.msg.cluster.ServerAddress;
  25 +import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg;
  26 +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg;
  27 +
  28 +import java.util.Optional;
  29 +
  30 +/**
  31 + * Created by ashvayka on 16.04.18.
  32 + */
  33 +@ToString
  34 +@RequiredArgsConstructor
  35 +public class ToServerRpcResponseActorMsg implements ToDeviceActorNotificationMsg {
  36 +
  37 + private final ServerAddress serverAddress;
  38 +
  39 + @Getter
  40 + private final TenantId tenantId;
  41 +
  42 + @Getter
  43 + private final DeviceId deviceId;
  44 +
  45 + @Getter
  46 + private final ToServerRpcResponseMsg msg;
  47 +
  48 + public ToServerRpcResponseActorMsg(TenantId tenantId, DeviceId deviceId, ToServerRpcResponseMsg msg) {
  49 + this(null, tenantId, deviceId, msg);
  50 + }
  51 +
  52 + public Optional<ServerAddress> getServerAddress() {
  53 + return Optional.ofNullable(serverAddress);
  54 + }
  55 +
  56 + @Override
  57 + public MsgType getMsgType() {
  58 + return MsgType.SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG;
  59 + }
  60 +}
... ...
... ... @@ -118,7 +118,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn
118 118 String newData = data != null ? data : msg.getData();
119 119 TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData();
120 120 String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType();
121   - return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData);
  121 + return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition());
122 122 } catch (Throwable th) {
123 123 th.printStackTrace();
124 124 throw new RuntimeException("Failed to unbind message data from javascript result", th);
... ...
... ... @@ -25,6 +25,7 @@ import org.springframework.http.ResponseEntity;
25 25 import org.springframework.stereotype.Component;
26 26 import org.springframework.web.context.request.async.DeferredResult;
27 27 import org.thingsboard.server.actors.plugin.ValidationResult;
  28 +import org.thingsboard.server.common.data.BaseData;
28 29 import org.thingsboard.server.common.data.Customer;
29 30 import org.thingsboard.server.common.data.Device;
30 31 import org.thingsboard.server.common.data.Tenant;
... ... @@ -35,8 +36,10 @@ import org.thingsboard.server.common.data.id.DeviceId;
35 36 import org.thingsboard.server.common.data.id.EntityId;
36 37 import org.thingsboard.server.common.data.id.EntityIdFactory;
37 38 import org.thingsboard.server.common.data.id.RuleChainId;
  39 +import org.thingsboard.server.common.data.id.RuleNodeId;
38 40 import org.thingsboard.server.common.data.id.TenantId;
39 41 import org.thingsboard.server.common.data.rule.RuleChain;
  42 +import org.thingsboard.server.common.data.rule.RuleNode;
40 43 import org.thingsboard.server.controller.HttpValidationCallback;
41 44 import org.thingsboard.server.dao.alarm.AlarmService;
42 45 import org.thingsboard.server.dao.asset.AssetService;
... ... @@ -140,7 +143,7 @@ public class AccessValidator {
140 143 return response;
141 144 }
142 145
143   - public <T> void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
  146 + public void validate(SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
144 147 switch (entityId.getEntityType()) {
145 148 case DEVICE:
146 149 validateDevice(currentUser, entityId, callback);
... ... @@ -177,14 +180,14 @@ public class AccessValidator {
177 180 } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) {
178 181 return ValidationResult.accessDenied("Device doesn't belong to the current Customer!");
179 182 } else {
180   - return ValidationResult.ok();
  183 + return ValidationResult.ok(device);
181 184 }
182 185 }
183 186 }), executor);
184 187 }
185 188 }
186 189
187   - private <T> void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
  190 + private void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
188 191 if (currentUser.isSystemAdmin()) {
189 192 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
190 193 } else {
... ... @@ -198,15 +201,14 @@ public class AccessValidator {
198 201 } else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) {
199 202 return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!");
200 203 } else {
201   - return ValidationResult.ok();
  204 + return ValidationResult.ok(asset);
202 205 }
203 206 }
204 207 }), executor);
205 208 }
206 209 }
207 210
208   -
209   - private <T> void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
  211 + private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
210 212 if (currentUser.isCustomerUser()) {
211 213 callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
212 214 } else {
... ... @@ -220,14 +222,40 @@ public class AccessValidator {
220 222 } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
221 223 return ValidationResult.accessDenied("Rule chain is not in system scope!");
222 224 } else {
223   - return ValidationResult.ok();
  225 + return ValidationResult.ok(ruleChain);
  226 + }
  227 + }
  228 + }), executor);
  229 + }
  230 + }
  231 +
  232 + private void validateRule(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
  233 + if (currentUser.isCustomerUser()) {
  234 + callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
  235 + } else {
  236 + ListenableFuture<RuleNode> ruleNodeFuture = ruleChainService.findRuleNodeByIdAsync(new RuleNodeId(entityId.getId()));
  237 + Futures.addCallback(ruleNodeFuture, getCallback(callback, ruleNodeTmp -> {
  238 + RuleNode ruleNode = ruleNodeTmp;
  239 + if (ruleNode == null) {
  240 + return ValidationResult.entityNotFound("Rule node with requested id wasn't found!");
  241 + } else if (ruleNode.getRuleChainId() == null) {
  242 + return ValidationResult.entityNotFound("Rule chain with requested node id wasn't found!");
  243 + } else {
  244 + //TODO: make async
  245 + RuleChain ruleChain = ruleChainService.findRuleChainById(ruleNode.getRuleChainId());
  246 + if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) {
  247 + return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!");
  248 + } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) {
  249 + return ValidationResult.accessDenied("Rule chain is not in system scope!");
  250 + } else {
  251 + return ValidationResult.ok(ruleNode);
224 252 }
225 253 }
226 254 }), executor);
227 255 }
228 256 }
229 257
230   - private <T> void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
  258 + private void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
231 259 if (currentUser.isSystemAdmin()) {
232 260 callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
233 261 } else {
... ... @@ -241,18 +269,18 @@ public class AccessValidator {
241 269 } else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) {
242 270 return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!");
243 271 } else {
244   - return ValidationResult.ok();
  272 + return ValidationResult.ok(customer);
245 273 }
246 274 }
247 275 }), executor);
248 276 }
249 277 }
250 278
251   - private <T> void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
  279 + private void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback<ValidationResult> callback) {
252 280 if (currentUser.isCustomerUser()) {
253 281 callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION));
254 282 } else if (currentUser.isSystemAdmin()) {
255   - callback.onSuccess(ValidationResult.ok());
  283 + callback.onSuccess(ValidationResult.ok(null));
256 284 } else {
257 285 ListenableFuture<Tenant> tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId()));
258 286 Futures.addCallback(tenantFuture, getCallback(callback, tenant -> {
... ... @@ -261,13 +289,13 @@ public class AccessValidator {
261 289 } else if (!tenant.getId().equals(currentUser.getTenantId())) {
262 290 return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!");
263 291 } else {
264   - return ValidationResult.ok();
  292 + return ValidationResult.ok(tenant);
265 293 }
266 294 }), executor);
267 295 }
268 296 }
269 297
270   - private <T> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult> transformer) {
  298 + private <T, V> FutureCallback<T> getCallback(FutureCallback<ValidationResult> callback, Function<T, ValidationResult<V>> transformer) {
271 299 return new FutureCallback<T>() {
272 300 @Override
273 301 public void onSuccess(@Nullable T result) {
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.state;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.google.common.base.Function;
  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 com.google.common.util.concurrent.ListeningScheduledExecutorService;
  25 +import com.google.common.util.concurrent.MoreExecutors;
  26 +import lombok.Getter;
  27 +import lombok.extern.slf4j.Slf4j;
  28 +import org.springframework.beans.factory.annotation.Autowired;
  29 +import org.springframework.beans.factory.annotation.Value;
  30 +import org.springframework.stereotype.Service;
  31 +import org.thingsboard.server.actors.service.ActorService;
  32 +import org.thingsboard.server.common.data.DataConstants;
  33 +import org.thingsboard.server.common.data.Device;
  34 +import org.thingsboard.server.common.data.Tenant;
  35 +import org.thingsboard.server.common.data.id.DeviceId;
  36 +import org.thingsboard.server.common.data.id.TenantId;
  37 +import org.thingsboard.server.common.data.kv.AttributeKvEntry;
  38 +import org.thingsboard.server.common.data.page.TextPageLink;
  39 +import org.thingsboard.server.common.msg.TbMsg;
  40 +import org.thingsboard.server.common.msg.TbMsgDataType;
  41 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  42 +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
  43 +import org.thingsboard.server.dao.attributes.AttributesService;
  44 +import org.thingsboard.server.dao.device.DeviceService;
  45 +import org.thingsboard.server.dao.tenant.TenantService;
  46 +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
  47 +
  48 +import javax.annotation.Nullable;
  49 +import javax.annotation.PostConstruct;
  50 +import javax.annotation.PreDestroy;
  51 +import java.util.ArrayList;
  52 +import java.util.Arrays;
  53 +import java.util.HashSet;
  54 +import java.util.List;
  55 +import java.util.Optional;
  56 +import java.util.Set;
  57 +import java.util.concurrent.ConcurrentHashMap;
  58 +import java.util.concurrent.ConcurrentMap;
  59 +import java.util.concurrent.ExecutionException;
  60 +import java.util.concurrent.Executors;
  61 +import java.util.concurrent.TimeUnit;
  62 +
  63 +import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT;
  64 +import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT;
  65 +import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT;
  66 +import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT;
  67 +
  68 +/**
  69 + * Created by ashvayka on 01.05.18.
  70 + */
  71 +@Service
  72 +@Slf4j
  73 +//TODO: refactor to use page links as cursor and not fetch all
  74 +public class DefaultDeviceStateService implements DeviceStateService {
  75 +
  76 + private static final ObjectMapper json = new ObjectMapper();
  77 + public static final String ACTIVITY_STATE = "active";
  78 + public static final String LAST_CONNECT_TIME = "lastConnectTime";
  79 + public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime";
  80 + public static final String LAST_ACTIVITY_TIME = "lastActivityTime";
  81 + public static final String INACTIVITY_ALARM_TIME = "inactivityAlarmTime";
  82 + public static final String INACTIVITY_TIMEOUT = "inactivityTimeout";
  83 +
  84 + public static final List<String> PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME, LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT);
  85 +
  86 + @Autowired
  87 + private TenantService tenantService;
  88 +
  89 + @Autowired
  90 + private DeviceService deviceService;
  91 +
  92 + @Autowired
  93 + private AttributesService attributesService;
  94 +
  95 + @Autowired
  96 + private ActorService actorService;
  97 +
  98 + @Autowired
  99 + private TelemetrySubscriptionService tsSubService;
  100 +
  101 + @Value("${state.defaultInactivityTimeoutInSec}")
  102 + @Getter
  103 + private long defaultInactivityTimeoutInSec;
  104 +
  105 + @Value("${state.defaultStateCheckIntervalInSec}")
  106 + @Getter
  107 + private long defaultStateCheckIntervalInSec;
  108 +
  109 +// TODO in v2.1
  110 +// @Value("${state.defaultStatePersistenceIntervalInSec}")
  111 +// @Getter
  112 +// private long defaultStatePersistenceIntervalInSec;
  113 +//
  114 +// @Value("${state.defaultStatePersistencePack}")
  115 +// @Getter
  116 +// private long defaultStatePersistencePack;
  117 +
  118 + private ListeningScheduledExecutorService queueExecutor;
  119 +
  120 + private ConcurrentMap<TenantId, Set<DeviceId>> tenantDevices = new ConcurrentHashMap<>();
  121 + private ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
  122 +
  123 + @PostConstruct
  124 + public void init() {
  125 + // Should be always single threaded due to absence of locks.
  126 + queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor());
  127 + queueExecutor.submit(this::initStateFromDB);
  128 + queueExecutor.scheduleAtFixedRate(this::updateState, defaultStateCheckIntervalInSec, defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
  129 + //TODO: schedule persistence in v2.1;
  130 + }
  131 +
  132 + @PreDestroy
  133 + public void stop() {
  134 + if (queueExecutor != null) {
  135 + queueExecutor.shutdownNow();
  136 + }
  137 + }
  138 +
  139 + @Override
  140 + public void onDeviceAdded(Device device) {
  141 + queueExecutor.submit(() -> onDeviceAddedSync(device));
  142 + }
  143 +
  144 + @Override
  145 + public void onDeviceUpdated(Device device) {
  146 + queueExecutor.submit(() -> onDeviceUpdatedSync(device));
  147 + }
  148 +
  149 + @Override
  150 + public void onDeviceConnect(DeviceId deviceId) {
  151 + queueExecutor.submit(() -> onDeviceConnectSync(deviceId));
  152 + }
  153 +
  154 + @Override
  155 + public void onDeviceActivity(DeviceId deviceId) {
  156 + queueExecutor.submit(() -> onDeviceActivitySync(deviceId));
  157 + }
  158 +
  159 + @Override
  160 + public void onDeviceDisconnect(DeviceId deviceId) {
  161 + queueExecutor.submit(() -> onDeviceDisconnectSync(deviceId));
  162 + }
  163 +
  164 + @Override
  165 + public void onDeviceDeleted(Device device) {
  166 + queueExecutor.submit(() -> onDeviceDeleted(device.getTenantId(), device.getId()));
  167 + }
  168 +
  169 + @Override
  170 + public void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
  171 + queueExecutor.submit(() -> onInactivityTimeoutUpdate(deviceId, inactivityTimeout));
  172 + }
  173 +
  174 + @Override
  175 + public Optional<DeviceState> getDeviceState(DeviceId deviceId) {
  176 + DeviceStateData state = deviceStates.get(deviceId);
  177 + if (state != null) {
  178 + return Optional.of(state.getState());
  179 + } else {
  180 + return Optional.empty();
  181 + }
  182 + }
  183 +
  184 + private void initStateFromDB() {
  185 + List<Tenant> tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData();
  186 + for (Tenant tenant : tenants) {
  187 + List<ListenableFuture<DeviceStateData>> fetchFutures = new ArrayList<>();
  188 + List<Device> devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData();
  189 + for (Device device : devices) {
  190 + fetchFutures.add(fetchDeviceState(device));
  191 + }
  192 + try {
  193 + Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState);
  194 + } catch (InterruptedException | ExecutionException e) {
  195 + log.warn("Failed to init device state service from DB", e);
  196 + }
  197 + }
  198 + }
  199 +
  200 + private void addDeviceUsingState(DeviceStateData state) {
  201 + tenantDevices.computeIfAbsent(state.getTenantId(), id -> ConcurrentHashMap.newKeySet()).add(state.getDeviceId());
  202 + deviceStates.put(state.getDeviceId(), state);
  203 + }
  204 +
  205 + private void updateState() {
  206 + long ts = System.currentTimeMillis();
  207 + Set<DeviceId> deviceIds = new HashSet<>(deviceStates.keySet());
  208 + for (DeviceId deviceId : deviceIds) {
  209 + DeviceStateData stateData = deviceStates.get(deviceId);
  210 + DeviceState state = stateData.getState();
  211 + state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
  212 + if (!state.isActive() && state.getLastInactivityAlarmTime() < state.getLastActivityTime()) {
  213 + state.setLastInactivityAlarmTime(ts);
  214 + pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
  215 + saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts);
  216 + saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
  217 + }
  218 + }
  219 + }
  220 +
  221 + private void onDeviceConnectSync(DeviceId deviceId) {
  222 + DeviceStateData stateData = deviceStates.get(deviceId);
  223 + if (stateData != null) {
  224 + long ts = System.currentTimeMillis();
  225 + stateData.getState().setLastConnectTime(ts);
  226 + pushRuleEngineMessage(stateData, CONNECT_EVENT);
  227 + saveAttribute(deviceId, LAST_CONNECT_TIME, ts);
  228 + }
  229 + }
  230 +
  231 + private void onDeviceDisconnectSync(DeviceId deviceId) {
  232 + DeviceStateData stateData = deviceStates.get(deviceId);
  233 + if (stateData != null) {
  234 + long ts = System.currentTimeMillis();
  235 + stateData.getState().setLastDisconnectTime(ts);
  236 + pushRuleEngineMessage(stateData, DISCONNECT_EVENT);
  237 + saveAttribute(deviceId, LAST_DISCONNECT_TIME, ts);
  238 + }
  239 + }
  240 +
  241 + private void onDeviceActivitySync(DeviceId deviceId) {
  242 + DeviceStateData stateData = deviceStates.get(deviceId);
  243 + if (stateData != null) {
  244 + DeviceState state = stateData.getState();
  245 + long ts = System.currentTimeMillis();
  246 + state.setActive(true);
  247 + stateData.getState().setLastActivityTime(ts);
  248 + pushRuleEngineMessage(stateData, ACTIVITY_EVENT);
  249 + saveAttribute(deviceId, LAST_ACTIVITY_TIME, ts);
  250 + saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
  251 + }
  252 + }
  253 +
  254 + private void onInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) {
  255 + if (inactivityTimeout == 0L) {
  256 + return;
  257 + }
  258 + DeviceStateData stateData = deviceStates.get(deviceId);
  259 + if (stateData != null) {
  260 + long ts = System.currentTimeMillis();
  261 + DeviceState state = stateData.getState();
  262 + state.setInactivityTimeout(inactivityTimeout);
  263 + boolean oldActive = state.isActive();
  264 + state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout());
  265 + if (!oldActive && state.isActive()) {
  266 + saveAttribute(deviceId, ACTIVITY_STATE, state.isActive());
  267 + }
  268 + }
  269 + }
  270 +
  271 + private void onDeviceAddedSync(Device device) {
  272 + Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() {
  273 + @Override
  274 + public void onSuccess(@Nullable DeviceStateData state) {
  275 + addDeviceUsingState(state);
  276 + }
  277 +
  278 + @Override
  279 + public void onFailure(Throwable t) {
  280 + log.warn("Failed to register device to the state service", t);
  281 + }
  282 + });
  283 + }
  284 +
  285 + private void onDeviceUpdatedSync(Device device) {
  286 + DeviceStateData stateData = deviceStates.get(device.getId());
  287 + if (stateData != null) {
  288 + TbMsgMetaData md = new TbMsgMetaData();
  289 + md.putValue("deviceName", device.getName());
  290 + md.putValue("deviceType", device.getType());
  291 + stateData.setMetaData(md);
  292 + }
  293 + }
  294 +
  295 + private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
  296 + deviceStates.remove(deviceId);
  297 + Set<DeviceId> deviceIds = tenantDevices.get(tenantId);
  298 + if (deviceIds != null) {
  299 + deviceIds.remove(deviceId);
  300 + if (deviceIds.isEmpty()) {
  301 + tenantDevices.remove(tenantId);
  302 + }
  303 + }
  304 + }
  305 +
  306 + private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
  307 + ListenableFuture<List<AttributeKvEntry>> attributes = attributesService.find(device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES);
  308 + return Futures.transform(attributes, new Function<List<AttributeKvEntry>, DeviceStateData>() {
  309 + @Nullable
  310 + @Override
  311 + public DeviceStateData apply(@Nullable List<AttributeKvEntry> attributes) {
  312 + long lastActivityTime = getAttributeValue(attributes, LAST_ACTIVITY_TIME, 0L);
  313 + long inactivityAlarmTime = getAttributeValue(attributes, INACTIVITY_ALARM_TIME, 0L);
  314 + long inactivityTimeout = getAttributeValue(attributes, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec));
  315 + boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout;
  316 + DeviceState deviceState = DeviceState.builder()
  317 + .active(active)
  318 + .lastConnectTime(getAttributeValue(attributes, LAST_CONNECT_TIME, 0L))
  319 + .lastDisconnectTime(getAttributeValue(attributes, LAST_DISCONNECT_TIME, 0L))
  320 + .lastActivityTime(lastActivityTime)
  321 + .lastInactivityAlarmTime(inactivityAlarmTime)
  322 + .inactivityTimeout(inactivityTimeout)
  323 + .build();
  324 + TbMsgMetaData md = new TbMsgMetaData();
  325 + md.putValue("deviceName", device.getName());
  326 + md.putValue("deviceType", device.getType());
  327 + return DeviceStateData.builder()
  328 + .tenantId(device.getTenantId())
  329 + .deviceId(device.getId())
  330 + .metaData(md)
  331 + .state(deviceState).build();
  332 + }
  333 + });
  334 + }
  335 +
  336 + private long getLastPersistTime(List<AttributeKvEntry> attributes) {
  337 + return attributes.stream().map(AttributeKvEntry::getLastUpdateTs).max(Long::compare).orElse(0L);
  338 + }
  339 +
  340 + private long getAttributeValue(List<AttributeKvEntry> attributes, String attributeName, long defaultValue) {
  341 + for (AttributeKvEntry attribute : attributes) {
  342 + if (attribute.getKey().equals(attributeName)) {
  343 + return attribute.getLongValue().orElse(defaultValue);
  344 + }
  345 + }
  346 + return defaultValue;
  347 + }
  348 +
  349 + private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) {
  350 + DeviceState state = stateData.getState();
  351 + try {
  352 + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData(), TbMsgDataType.JSON
  353 + , json.writeValueAsString(state)
  354 + , null, null, 0L);
  355 + actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg));
  356 + } catch (Exception e) {
  357 + log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
  358 + }
  359 + }
  360 +
  361 + private void saveAttribute(DeviceId deviceId, String key, long value) {
  362 + tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value));
  363 + }
  364 +
  365 + private void saveAttribute(DeviceId deviceId, String key, boolean value) {
  366 + tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value));
  367 + }
  368 +
  369 + private class AttributeSaveCallback implements FutureCallback<Void> {
  370 + private final DeviceId deviceId;
  371 + private final String key;
  372 + private final Object value;
  373 +
  374 + AttributeSaveCallback(DeviceId deviceId, String key, Object value) {
  375 + this.deviceId = deviceId;
  376 + this.key = key;
  377 + this.value = value;
  378 + }
  379 +
  380 + @Override
  381 + public void onSuccess(@Nullable Void result) {
  382 + log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value);
  383 + }
  384 +
  385 + @Override
  386 + public void onFailure(Throwable t) {
  387 + log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t);
  388 + }
  389 + }
  390 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.state;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +
  21 +/**
  22 + * Created by ashvayka on 01.05.18.
  23 + */
  24 +@Data
  25 +@Builder
  26 +public class DeviceState {
  27 +
  28 + private boolean active;
  29 + private long lastConnectTime;
  30 + private long lastActivityTime;
  31 + private long lastDisconnectTime;
  32 + private long lastInactivityAlarmTime;
  33 + private long inactivityTimeout;
  34 +
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.state;
  17 +
  18 +import lombok.Builder;
  19 +import lombok.Data;
  20 +import org.thingsboard.server.common.data.id.DeviceId;
  21 +import org.thingsboard.server.common.data.id.TenantId;
  22 +import org.thingsboard.server.common.msg.TbMsgMetaData;
  23 +
  24 +/**
  25 + * Created by ashvayka on 01.05.18.
  26 + */
  27 +@Data
  28 +@Builder
  29 +class DeviceStateData {
  30 +
  31 + private final TenantId tenantId;
  32 + private final DeviceId deviceId;
  33 +
  34 + private TbMsgMetaData metaData;
  35 + private final DeviceState state;
  36 +
  37 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 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.state;
  17 +
  18 +import org.thingsboard.server.common.data.Device;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
  20 +
  21 +import java.util.Optional;
  22 +
  23 +/**
  24 + * Created by ashvayka on 01.05.18.
  25 + */
  26 +public interface DeviceStateService {
  27 +
  28 + void onDeviceAdded(Device device);
  29 +
  30 + void onDeviceUpdated(Device device);
  31 +
  32 + void onDeviceDeleted(Device device);
  33 +
  34 + void onDeviceConnect(DeviceId deviceId);
  35 +
  36 + void onDeviceActivity(DeviceId deviceId);
  37 +
  38 + void onDeviceDisconnect(DeviceId deviceId);
  39 +
  40 + void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout);
  41 +
  42 + Optional<DeviceState> getDeviceState(DeviceId deviceId);
  43 +
  44 +}
... ...