committed by
GitHub
954 changed files with 28306 additions and 24639 deletions
@ -1,13 +0,0 @@ |
|||||
{ |
|
||||
"apiToken": "messaging", |
|
||||
"name": "Demo Device Messaging RPC Plugin", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.plugin.messaging.DeviceMessagingPlugin", |
|
||||
"publicAccess": false, |
|
||||
"state": "ACTIVE", |
|
||||
"configuration": { |
|
||||
"maxDeviceCountPerCustomer": 1024, |
|
||||
"defaultTimeout": 20000, |
|
||||
"maxTimeout": 60000 |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,28 +0,0 @@ |
|||||
{ |
|
||||
"apiToken": "mail", |
|
||||
"name": "Demo Email Plugin", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.plugin.mail.MailPlugin", |
|
||||
"publicAccess": true, |
|
||||
"state": "ACTIVE", |
|
||||
"configuration": { |
|
||||
"host": "smtp.sendgrid.net", |
|
||||
"port": 2525, |
|
||||
"username": "apikey", |
|
||||
"password": "your_api_key", |
|
||||
"otherProperties": [ |
|
||||
{ |
|
||||
"key": "mail.smtp.auth", |
|
||||
"value": "true" |
|
||||
}, |
|
||||
{ |
|
||||
"key": "mail.smtp.timeout", |
|
||||
"value": "10000" |
|
||||
}, |
|
||||
{ |
|
||||
"key": "mail.smtp.starttls.enable", |
|
||||
"value": "true" |
|
||||
} |
|
||||
] |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,11 +0,0 @@ |
|||||
{ |
|
||||
"apiToken": "time", |
|
||||
"name": "Demo Time RPC Plugin", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.plugin.time.TimePlugin", |
|
||||
"publicAccess": false, |
|
||||
"state": "ACTIVE", |
|
||||
"configuration": { |
|
||||
"timeFormat": "yyyy MM dd HH:mm:ss.SSS" |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,46 +0,0 @@ |
|||||
{ |
|
||||
"name": "Demo Alarm Rule", |
|
||||
"state": "ACTIVE", |
|
||||
"weight": 0, |
|
||||
"pluginToken": "mail", |
|
||||
"filters": [ |
|
||||
{ |
|
||||
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter", |
|
||||
"name": "MsgTypeFilter", |
|
||||
"configuration": { |
|
||||
"messageTypes": [ |
|
||||
"POST_TELEMETRY", |
|
||||
"POST_ATTRIBUTES", |
|
||||
"GET_ATTRIBUTES" |
|
||||
] |
|
||||
} |
|
||||
}, |
|
||||
{ |
|
||||
"clazz": "org.thingsboard.server.extensions.core.filter.DeviceTelemetryFilter", |
|
||||
"name": "Temperature filter", |
|
||||
"configuration": { |
|
||||
"filter": "typeof temperature !== 'undefined' && temperature >= 100" |
|
||||
} |
|
||||
} |
|
||||
], |
|
||||
"processor": { |
|
||||
"clazz": "org.thingsboard.server.extensions.core.processor.AlarmDeduplicationProcessor", |
|
||||
"name": "AlarmDeduplicationProcessor", |
|
||||
"configuration": { |
|
||||
"alarmIdTemplate": "[$date.get('yyyy-MM-dd HH:mm')] Device $cs.get('serialNumber')($cs.get('model')) temperature is high!", |
|
||||
"alarmBodyTemplate": "[$date.get('yyyy-MM-dd HH:mm:ss')] Device $cs.get('serialNumber')($cs.get('model')) temperature is $temp.valueAsString!" |
|
||||
} |
|
||||
}, |
|
||||
"action": { |
|
||||
"clazz": "org.thingsboard.server.extensions.core.action.mail.SendMailAction", |
|
||||
"name": "Send Mail Action", |
|
||||
"configuration": { |
|
||||
"sendFlag": "isNewAlarm", |
|
||||
"fromTemplate": "thingsboard@gmail.com", |
|
||||
"toTemplate": "thingsboard@gmail.com", |
|
||||
"subjectTemplate": "$alarmId", |
|
||||
"bodyTemplate": "$alarmBody" |
|
||||
} |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,35 +0,0 @@ |
|||||
{ |
|
||||
"name": "Demo getTime RPC Rule", |
|
||||
"state": "ACTIVE", |
|
||||
"weight": 0, |
|
||||
"pluginToken": "time", |
|
||||
"filters": [ |
|
||||
{ |
|
||||
"configuration": { |
|
||||
"messageTypes": [ |
|
||||
"RPC_REQUEST" |
|
||||
] |
|
||||
}, |
|
||||
"name": "RPC Request Filter", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter" |
|
||||
}, |
|
||||
{ |
|
||||
"configuration": { |
|
||||
"methodNames": [ |
|
||||
{ |
|
||||
"name": "getTime" |
|
||||
} |
|
||||
] |
|
||||
}, |
|
||||
"name": "getTime method filter", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter" |
|
||||
} |
|
||||
], |
|
||||
"processor": null, |
|
||||
"action": { |
|
||||
"configuration": {}, |
|
||||
"clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction", |
|
||||
"name": "getTimeAction" |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,38 +0,0 @@ |
|||||
{ |
|
||||
"name": "Demo Messaging RPC Rule", |
|
||||
"state": "ACTIVE", |
|
||||
"weight": 0, |
|
||||
"pluginToken": "messaging", |
|
||||
"filters": [ |
|
||||
{ |
|
||||
"configuration": { |
|
||||
"messageTypes": [ |
|
||||
"RPC_REQUEST" |
|
||||
] |
|
||||
}, |
|
||||
"name": "RPC Request Filter", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter" |
|
||||
}, |
|
||||
{ |
|
||||
"configuration": { |
|
||||
"methodNames": [ |
|
||||
{ |
|
||||
"name": "getDevices" |
|
||||
}, |
|
||||
{ |
|
||||
"name": "sendMsg" |
|
||||
} |
|
||||
] |
|
||||
}, |
|
||||
"name": "Messaging methods filter", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.filter.MethodNameFilter" |
|
||||
} |
|
||||
], |
|
||||
"processor": null, |
|
||||
"action": { |
|
||||
"configuration": {}, |
|
||||
"clazz": "org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction", |
|
||||
"name": "Messaging RPC Action" |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,11 +0,0 @@ |
|||||
{ |
|
||||
"apiToken": "rpc", |
|
||||
"name": "System RPC Plugin", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.plugin.rpc.RpcPlugin", |
|
||||
"publicAccess": true, |
|
||||
"state": "ACTIVE", |
|
||||
"configuration": { |
|
||||
"defaultTimeout": 20000 |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,9 +0,0 @@ |
|||||
{ |
|
||||
"apiToken": "telemetry", |
|
||||
"name": "System Telemetry Plugin", |
|
||||
"clazz": "org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin", |
|
||||
"publicAccess": true, |
|
||||
"state": "ACTIVE", |
|
||||
"configuration": {}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -1,29 +0,0 @@ |
|||||
{ |
|
||||
"name": "System Telemetry Rule", |
|
||||
"state": "ACTIVE", |
|
||||
"weight": 0, |
|
||||
"pluginToken": "telemetry", |
|
||||
"filters": [ |
|
||||
{ |
|
||||
"clazz": "org.thingsboard.server.extensions.core.filter.MsgTypeFilter", |
|
||||
"name": "TelemetryFilter", |
|
||||
"configuration": { |
|
||||
"messageTypes": [ |
|
||||
"POST_TELEMETRY", |
|
||||
"POST_ATTRIBUTES", |
|
||||
"GET_ATTRIBUTES" |
|
||||
] |
|
||||
} |
|
||||
} |
|
||||
], |
|
||||
"processor": null, |
|
||||
"action": { |
|
||||
"clazz": "org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction", |
|
||||
"name": "TelemetryMsgConverterAction", |
|
||||
"configuration": { |
|
||||
"timeUnit": "DAYS", |
|
||||
"ttlValue": 365 |
|
||||
} |
|
||||
}, |
|
||||
"additionalInfo": null |
|
||||
} |
|
||||
@ -0,0 +1,98 @@ |
|||||
|
{ |
||||
|
"ruleChain": { |
||||
|
"additionalInfo": null, |
||||
|
"name": "Root Rule Chain", |
||||
|
"firstRuleNodeId": null, |
||||
|
"root": true, |
||||
|
"debugMode": false, |
||||
|
"configuration": null |
||||
|
}, |
||||
|
"metadata": { |
||||
|
"firstNodeIndex": 2, |
||||
|
"nodes": [ |
||||
|
{ |
||||
|
"additionalInfo": { |
||||
|
"layoutX": 824, |
||||
|
"layoutY": 156 |
||||
|
}, |
||||
|
"type": "org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode", |
||||
|
"name": "SaveTS", |
||||
|
"debugMode": false, |
||||
|
"configuration": { |
||||
|
"defaultTTL": 0 |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"additionalInfo": { |
||||
|
"layoutX": 825, |
||||
|
"layoutY": 52 |
||||
|
}, |
||||
|
"type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", |
||||
|
"name": "save client attributes", |
||||
|
"debugMode": false, |
||||
|
"configuration": { |
||||
|
"scope": "CLIENT_SCOPE" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"additionalInfo": { |
||||
|
"layoutX": 347, |
||||
|
"layoutY": 149 |
||||
|
}, |
||||
|
"type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", |
||||
|
"name": "Message Type Switch", |
||||
|
"debugMode": false, |
||||
|
"configuration": { |
||||
|
"version": 0 |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"additionalInfo": { |
||||
|
"layoutX": 825, |
||||
|
"layoutY": 266 |
||||
|
}, |
||||
|
"type": "org.thingsboard.rule.engine.action.TbLogNode", |
||||
|
"name": "Log RPC", |
||||
|
"debugMode": false, |
||||
|
"configuration": { |
||||
|
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"additionalInfo": { |
||||
|
"layoutX": 825, |
||||
|
"layoutY": 379 |
||||
|
}, |
||||
|
"type": "org.thingsboard.rule.engine.action.TbLogNode", |
||||
|
"name": "Log Other", |
||||
|
"debugMode": false, |
||||
|
"configuration": { |
||||
|
"jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
"connections": [ |
||||
|
{ |
||||
|
"fromIndex": 2, |
||||
|
"toIndex": 4, |
||||
|
"type": "Other" |
||||
|
}, |
||||
|
{ |
||||
|
"fromIndex": 2, |
||||
|
"toIndex": 1, |
||||
|
"type": "Post attributes" |
||||
|
}, |
||||
|
{ |
||||
|
"fromIndex": 2, |
||||
|
"toIndex": 0, |
||||
|
"type": "Post telemetry" |
||||
|
}, |
||||
|
{ |
||||
|
"fromIndex": 2, |
||||
|
"toIndex": 3, |
||||
|
"type": "RPC Request" |
||||
|
} |
||||
|
], |
||||
|
"ruleChainConnections": null |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2018 The Thingsboard Authors |
||||
|
-- |
||||
|
-- Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
-- you may not use this file except in compliance with the License. |
||||
|
-- You may obtain a copy of the License at |
||||
|
-- |
||||
|
-- http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
-- |
||||
|
-- Unless required by applicable law or agreed to in writing, software |
||||
|
-- distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
-- See the License for the specific language governing permissions and |
||||
|
-- limitations under the License. |
||||
|
-- |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( |
||||
|
node_id timeuuid, |
||||
|
cluster_partition bigint, |
||||
|
ts_partition bigint, |
||||
|
ts bigint, |
||||
|
msg blob, |
||||
|
PRIMARY KEY ((node_id, cluster_partition, ts_partition), ts)) |
||||
|
WITH CLUSTERING ORDER BY (ts DESC) |
||||
|
AND compaction = { |
||||
|
'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', |
||||
|
'min_threshold': '5', |
||||
|
'base_time_seconds': '43200', |
||||
|
'max_window_size_seconds': '43200', |
||||
|
'tombstone_threshold': '0.9', |
||||
|
'unchecked_tombstone_compaction': 'true' |
||||
|
}; |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( |
||||
|
node_id timeuuid, |
||||
|
cluster_partition bigint, |
||||
|
ts_partition bigint, |
||||
|
msg_id timeuuid, |
||||
|
PRIMARY KEY ((node_id, cluster_partition, ts_partition), msg_id)) |
||||
|
WITH CLUSTERING ORDER BY (msg_id DESC) |
||||
|
AND compaction = { |
||||
|
'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', |
||||
|
'min_threshold': '5', |
||||
|
'base_time_seconds': '43200', |
||||
|
'max_window_size_seconds': '43200', |
||||
|
'tombstone_threshold': '0.9', |
||||
|
'unchecked_tombstone_compaction': 'true' |
||||
|
}; |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( |
||||
|
node_id timeuuid, |
||||
|
cluster_partition bigint, |
||||
|
ts_partition bigint, |
||||
|
PRIMARY KEY ((node_id, cluster_partition), ts_partition)) |
||||
|
WITH CLUSTERING ORDER BY (ts_partition DESC) |
||||
|
AND compaction = { |
||||
|
'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', |
||||
|
'min_threshold': '5', |
||||
|
'base_time_seconds': '43200', |
||||
|
'max_window_size_seconds': '43200', |
||||
|
'tombstone_threshold': '0.9', |
||||
|
'unchecked_tombstone_compaction': 'true' |
||||
|
}; |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS thingsboard.rule_chain ( |
||||
|
id uuid, |
||||
|
tenant_id uuid, |
||||
|
name text, |
||||
|
search_text text, |
||||
|
first_rule_node_id uuid, |
||||
|
root boolean, |
||||
|
debug_mode boolean, |
||||
|
configuration text, |
||||
|
additional_info text, |
||||
|
PRIMARY KEY (id, tenant_id) |
||||
|
); |
||||
|
|
||||
|
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_search_text AS |
||||
|
SELECT * |
||||
|
from thingsboard.rule_chain |
||||
|
WHERE tenant_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL |
||||
|
PRIMARY KEY ( tenant_id, search_text, id ) |
||||
|
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC ); |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( |
||||
|
id uuid, |
||||
|
rule_chain_id uuid, |
||||
|
type text, |
||||
|
name text, |
||||
|
debug_mode boolean, |
||||
|
search_text text, |
||||
|
configuration text, |
||||
|
additional_info text, |
||||
|
PRIMARY KEY (id) |
||||
|
); |
||||
|
|
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.rule_by_plugin_token; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.rule_by_tenant_and_search_text; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.plugin_by_api_token; |
||||
|
DROP MATERIALIZED VIEW IF EXISTS thingsboard.plugin_by_tenant_and_search_text; |
||||
|
|
||||
|
DROP TABLE IF EXISTS thingsboard.rule; |
||||
|
DROP TABLE IF EXISTS thingsboard.plugin; |
||||
@ -0,0 +1,44 @@ |
|||||
|
-- |
||||
|
-- Copyright © 2016-2018 The Thingsboard Authors |
||||
|
-- |
||||
|
-- Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
-- you may not use this file except in compliance with the License. |
||||
|
-- You may obtain a copy of the License at |
||||
|
-- |
||||
|
-- http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
-- |
||||
|
-- Unless required by applicable law or agreed to in writing, software |
||||
|
-- distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
-- See the License for the specific language governing permissions and |
||||
|
-- limitations under the License. |
||||
|
-- |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS rule_chain ( |
||||
|
id varchar(31) NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY, |
||||
|
additional_info varchar, |
||||
|
configuration varchar(10000000), |
||||
|
name varchar(255), |
||||
|
first_rule_node_id varchar(31), |
||||
|
root boolean, |
||||
|
debug_mode boolean, |
||||
|
search_text varchar(255), |
||||
|
tenant_id varchar(31) |
||||
|
); |
||||
|
|
||||
|
CREATE TABLE IF NOT EXISTS rule_node ( |
||||
|
id varchar(31) NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY, |
||||
|
rule_chain_id varchar(31), |
||||
|
additional_info varchar, |
||||
|
configuration varchar(10000000), |
||||
|
type varchar(255), |
||||
|
name varchar(255), |
||||
|
debug_mode boolean, |
||||
|
search_text varchar(255) |
||||
|
); |
||||
|
|
||||
|
DROP TABLE rule; |
||||
|
DROP TABLE plugin; |
||||
|
|
||||
|
DELETE FROM alarm WHERE originator_type = 3 OR originator_type = 4; |
||||
|
UPDATE alarm SET originator_type = (originator_type - 2) where originator_type > 2; |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.device; |
||||
|
|
||||
|
import akka.actor.ActorRef; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.msg.MsgType; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 15.03.18. |
||||
|
*/ |
||||
|
@Data |
||||
|
public final class DeviceActorToRuleEngineMsg implements TbActorMsg { |
||||
|
|
||||
|
private final ActorRef callbackRef; |
||||
|
private final TbMsg tbMsg; |
||||
|
|
||||
|
@Override |
||||
|
public MsgType getMsgType() { |
||||
|
return MsgType.DEVICE_ACTOR_TO_RULE_ENGINE_MSG; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.device; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.id.SessionId; |
||||
|
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
||||
|
import org.thingsboard.server.common.msg.session.SessionMsgType; |
||||
|
|
||||
|
import java.util.Optional; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 17.04.18. |
||||
|
*/ |
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
public final class PendingSessionMsgData { |
||||
|
|
||||
|
private final SessionId sessionId; |
||||
|
private final Optional<ServerAddress> serverAddress; |
||||
|
private final SessionMsgType sessionMsgType; |
||||
|
private final int requestId; |
||||
|
private final boolean replyOnQueueAck; |
||||
|
private int ackMsgCount; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.device; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.msg.MsgType; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
|
||||
|
import java.util.UUID; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 15.03.18. |
||||
|
*/ |
||||
|
@Data |
||||
|
public final class RuleEngineQueuePutAckMsg implements TbActorMsg { |
||||
|
|
||||
|
private final UUID id; |
||||
|
|
||||
|
@Override |
||||
|
public MsgType getMsgType() { |
||||
|
return MsgType.RULE_ENGINE_QUEUE_PUT_ACK_MSG; |
||||
|
} |
||||
|
} |
||||
@ -1,151 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.actor.ActorRef; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.service.ComponentActor; |
|
||||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|
||||
import org.thingsboard.server.actors.stats.StatsPersistTick; |
|
||||
import org.thingsboard.server.common.data.id.PluginId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|
||||
import org.thingsboard.server.extensions.api.rules.RuleException; |
|
||||
|
|
||||
public class PluginActor extends ComponentActor<PluginId, PluginActorMessageProcessor> { |
|
||||
|
|
||||
private PluginActor(ActorSystemContext systemContext, TenantId tenantId, PluginId pluginId) { |
|
||||
super(systemContext, tenantId, pluginId); |
|
||||
setProcessor(new PluginActorMessageProcessor(tenantId, pluginId, systemContext, |
|
||||
logger, context().parent(), context().self())); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onReceive(Object msg) throws Exception { |
|
||||
if (msg instanceof PluginWebsocketMsg) { |
|
||||
onWebsocketMsg((PluginWebsocketMsg<?>) msg); |
|
||||
} else if (msg instanceof PluginRestMsg) { |
|
||||
onRestMsg((PluginRestMsg) msg); |
|
||||
} else if (msg instanceof PluginCallbackMessage) { |
|
||||
onPluginCallback((PluginCallbackMessage) msg); |
|
||||
} else if (msg instanceof RuleToPluginMsgWrapper) { |
|
||||
onRuleToPluginMsg((RuleToPluginMsgWrapper) msg); |
|
||||
} else if (msg instanceof PluginRpcMsg) { |
|
||||
onRpcMsg((PluginRpcMsg) msg); |
|
||||
} else if (msg instanceof ClusterEventMsg) { |
|
||||
onClusterEventMsg((ClusterEventMsg) msg); |
|
||||
} else if (msg instanceof ComponentLifecycleMsg) { |
|
||||
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
|
||||
} else if (msg instanceof ToPluginRpcResponseDeviceMsg) { |
|
||||
onRpcResponse((ToPluginRpcResponseDeviceMsg) msg); |
|
||||
} else if (msg instanceof PluginTerminationMsg) { |
|
||||
logger.info("[{}][{}] Going to terminate plugin actor.", tenantId, id); |
|
||||
context().parent().tell(msg, ActorRef.noSender()); |
|
||||
context().stop(self()); |
|
||||
} else if (msg instanceof TimeoutMsg) { |
|
||||
onTimeoutMsg(context(), (TimeoutMsg) msg); |
|
||||
} else if (msg instanceof StatsPersistTick) { |
|
||||
onStatsPersistTick(id); |
|
||||
} else { |
|
||||
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onPluginCallback(PluginCallbackMessage msg) { |
|
||||
try { |
|
||||
processor.onPluginCallbackMsg(msg); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onPluginCallbackMsg", e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onTimeoutMsg(ActorContext context, TimeoutMsg msg) { |
|
||||
processor.onTimeoutMsg(context, msg); |
|
||||
} |
|
||||
|
|
||||
private void onRpcResponse(ToPluginRpcResponseDeviceMsg msg) { |
|
||||
processor.onDeviceRpcMsg(msg.getResponse()); |
|
||||
} |
|
||||
|
|
||||
private void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException { |
|
||||
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg()); |
|
||||
try { |
|
||||
processor.onRuleToPluginMsg(msg); |
|
||||
increaseMessagesProcessedCount(); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onRuleMsg", e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onWebsocketMsg(PluginWebsocketMsg<?> msg) { |
|
||||
logger.debug("[{}] Going to process web socket msg: {}", id, msg); |
|
||||
try { |
|
||||
processor.onWebsocketMsg(msg); |
|
||||
increaseMessagesProcessedCount(); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onWebsocketMsg", e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onRestMsg(PluginRestMsg msg) { |
|
||||
logger.debug("[{}] Going to process rest msg: {}", id, msg); |
|
||||
try { |
|
||||
processor.onRestMsg(msg); |
|
||||
increaseMessagesProcessedCount(); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onRestMsg", e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void onRpcMsg(PluginRpcMsg msg) { |
|
||||
try { |
|
||||
logger.debug("[{}] Going to process rpc msg: {}", id, msg); |
|
||||
processor.onRpcMsg(msg); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onRpcMsg", e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static class ActorCreator extends ContextBasedCreator<PluginActor> { |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
private final TenantId tenantId; |
|
||||
private final PluginId pluginId; |
|
||||
|
|
||||
public ActorCreator(ActorSystemContext context, TenantId tenantId, PluginId pluginId) { |
|
||||
super(context); |
|
||||
this.tenantId = tenantId; |
|
||||
this.pluginId = pluginId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public PluginActor create() throws Exception { |
|
||||
return new PluginActor(context, tenantId, pluginId); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected long getErrorPersistFrequency() { |
|
||||
return systemContext.getPluginErrorPersistFrequency(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,251 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.actor.ActorRef; |
|
||||
import akka.event.LoggingAdapter; |
|
||||
import com.fasterxml.jackson.core.JsonProcessingException; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
|
||||
import org.thingsboard.server.common.data.id.PluginId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
|
||||
import org.thingsboard.server.common.data.plugin.ComponentType; |
|
||||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|
||||
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; |
|
||||
import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; |
|
||||
import org.thingsboard.server.common.msg.session.MsgType; |
|
||||
import org.thingsboard.server.extensions.api.plugins.Plugin; |
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginInitializationException; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|
||||
import org.thingsboard.server.extensions.api.rules.RuleException; |
|
||||
|
|
||||
/** |
|
||||
* @author Andrew Shvayka |
|
||||
*/ |
|
||||
public class PluginActorMessageProcessor extends ComponentMsgProcessor<PluginId> { |
|
||||
|
|
||||
private final SharedPluginProcessingContext pluginCtx; |
|
||||
private final PluginProcessingContext trustedCtx; |
|
||||
private PluginMetaData pluginMd; |
|
||||
private Plugin pluginImpl; |
|
||||
private ComponentLifecycleState state; |
|
||||
|
|
||||
|
|
||||
protected PluginActorMessageProcessor(TenantId tenantId, PluginId pluginId, ActorSystemContext systemContext |
|
||||
, LoggingAdapter logger, ActorRef parent, ActorRef self) { |
|
||||
super(systemContext, logger, tenantId, pluginId); |
|
||||
this.pluginCtx = new SharedPluginProcessingContext(systemContext, tenantId, pluginId, parent, self); |
|
||||
this.trustedCtx = new PluginProcessingContext(pluginCtx, null); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void start() throws Exception { |
|
||||
logger.info("[{}] Going to start plugin actor.", entityId); |
|
||||
pluginMd = systemContext.getPluginService().findPluginById(entityId); |
|
||||
if (pluginMd == null) { |
|
||||
throw new PluginInitializationException("Plugin not found!"); |
|
||||
} |
|
||||
if (pluginMd.getConfiguration() == null) { |
|
||||
throw new PluginInitializationException("Plugin metadata is empty!"); |
|
||||
} |
|
||||
state = pluginMd.getState(); |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
logger.info("[{}] Plugin is active. Going to initialize plugin.", entityId); |
|
||||
initComponent(); |
|
||||
} else { |
|
||||
logger.info("[{}] Plugin is suspended. Skipping plugin initialization.", entityId); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void stop() throws Exception { |
|
||||
onStop(); |
|
||||
} |
|
||||
|
|
||||
private void initComponent() { |
|
||||
try { |
|
||||
pluginImpl = initComponent(pluginMd.getClazz(), ComponentType.PLUGIN, mapper.writeValueAsString(pluginMd.getConfiguration())); |
|
||||
} catch (InstantiationException e) { |
|
||||
throw new PluginInitializationException("No default constructor for plugin implementation!", e); |
|
||||
} catch (IllegalAccessException e) { |
|
||||
throw new PluginInitializationException("Illegal Access Exception during plugin initialization!", e); |
|
||||
} catch (ClassNotFoundException e) { |
|
||||
throw new PluginInitializationException("Plugin Class not found!", e); |
|
||||
} catch (JsonProcessingException e) { |
|
||||
throw new PluginInitializationException("Plugin Configuration is invalid!", e); |
|
||||
} catch (Exception e) { |
|
||||
throw new PluginInitializationException(e.getMessage(), e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void onRuleToPluginMsg(RuleToPluginMsgWrapper msg) throws RuleException { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
try { |
|
||||
pluginImpl.process(trustedCtx, msg.getRuleTenantId(), msg.getRuleId(), msg.getMsg()); |
|
||||
} catch (Exception ex) { |
|
||||
logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex); |
|
||||
RuleToPluginMsg ruleMsg = msg.getMsg(); |
|
||||
MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR; |
|
||||
Integer requestId = 0; |
|
||||
if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) { |
|
||||
requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId(); |
|
||||
} |
|
||||
trustedCtx.reply( |
|
||||
new ResponsePluginToRuleMsg(ruleMsg.getUid(), tenantId, msg.getRuleId(), |
|
||||
BasicStatusCodeResponse.onError(responceMsgType, requestId, ex))); |
|
||||
} |
|
||||
} else { |
|
||||
//TODO: reply with plugin suspended message
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void onWebsocketMsg(PluginWebsocketMsg<?> msg) { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg); |
|
||||
} else { |
|
||||
//TODO: reply with plugin suspended message
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void onRestMsg(PluginRestMsg msg) { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
pluginImpl.process(new PluginProcessingContext(pluginCtx, msg.getSecurityCtx()), msg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void onRpcMsg(PluginRpcMsg msg) { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
pluginImpl.process(trustedCtx, msg.getRpcMsg()); |
|
||||
} else { |
|
||||
//TODO: reply with plugin suspended message
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void onPluginCallbackMsg(PluginCallbackMessage msg) { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
if (msg.isSuccess()) { |
|
||||
msg.getCallback().onSuccess(trustedCtx, msg.getV()); |
|
||||
} else { |
|
||||
msg.getCallback().onFailure(trustedCtx, msg.getE()); |
|
||||
} |
|
||||
} else { |
|
||||
//TODO: reply with plugin suspended message
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public void onTimeoutMsg(ActorContext context, TimeoutMsg<?> msg) { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
pluginImpl.process(trustedCtx, msg); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public void onDeviceRpcMsg(FromDeviceRpcResponse response) { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
pluginImpl.process(trustedCtx, response); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onClusterEventMsg(ClusterEventMsg msg) { |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
ServerAddress address = msg.getServerAddress(); |
|
||||
if (msg.isAdded()) { |
|
||||
logger.debug("[{}] Going to process server add msg: {}", entityId, address); |
|
||||
pluginImpl.onServerAdded(trustedCtx, address); |
|
||||
} else { |
|
||||
logger.debug("[{}] Going to process server remove msg: {}", entityId, address); |
|
||||
pluginImpl.onServerRemoved(trustedCtx, address); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onCreated(ActorContext context) { |
|
||||
logger.info("[{}] Going to process onCreated plugin.", entityId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onUpdate(ActorContext context) throws Exception { |
|
||||
PluginMetaData oldPluginMd = pluginMd; |
|
||||
pluginMd = systemContext.getPluginService().findPluginById(entityId); |
|
||||
boolean requiresRestart = false; |
|
||||
logger.info("[{}] Plugin configuration was updated from {} to {}.", entityId, oldPluginMd, pluginMd); |
|
||||
if (!oldPluginMd.getClazz().equals(pluginMd.getClazz())) { |
|
||||
logger.info("[{}] Plugin requires restart due to clazz change from {} to {}.", |
|
||||
entityId, oldPluginMd.getClazz(), pluginMd.getClazz()); |
|
||||
requiresRestart = true; |
|
||||
} else if (!oldPluginMd.getConfiguration().equals(pluginMd.getConfiguration())) { |
|
||||
logger.info("[{}] Plugin requires restart due to configuration change from {} to {}.", |
|
||||
entityId, oldPluginMd.getConfiguration(), pluginMd.getConfiguration()); |
|
||||
requiresRestart = true; |
|
||||
} |
|
||||
if (requiresRestart) { |
|
||||
this.state = ComponentLifecycleState.SUSPENDED; |
|
||||
if (pluginImpl != null) { |
|
||||
pluginImpl.stop(trustedCtx); |
|
||||
} |
|
||||
start(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onStop(ActorContext context) { |
|
||||
onStop(); |
|
||||
scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay()); |
|
||||
} |
|
||||
|
|
||||
private void onStop() { |
|
||||
logger.info("[{}] Going to process onStop plugin.", entityId); |
|
||||
this.state = ComponentLifecycleState.SUSPENDED; |
|
||||
if (pluginImpl != null) { |
|
||||
pluginImpl.stop(trustedCtx); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onActivate(ActorContext context) throws Exception { |
|
||||
logger.info("[{}] Going to process onActivate plugin.", entityId); |
|
||||
this.state = ComponentLifecycleState.ACTIVE; |
|
||||
if (pluginImpl != null) { |
|
||||
pluginImpl.resume(trustedCtx); |
|
||||
logger.info("[{}] Plugin resumed.", entityId); |
|
||||
} else { |
|
||||
start(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onSuspend(ActorContext context) { |
|
||||
logger.info("[{}] Going to process onSuspend plugin.", entityId); |
|
||||
this.state = ComponentLifecycleState.SUSPENDED; |
|
||||
if (pluginImpl != null) { |
|
||||
pluginImpl.suspend(trustedCtx); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,53 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import lombok.Data; |
|
||||
import lombok.Getter; |
|
||||
import lombok.ToString; |
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginCallback; |
|
||||
|
|
||||
import java.util.Optional; |
|
||||
|
|
||||
/** |
|
||||
* @author Andrew Shvayka |
|
||||
*/ |
|
||||
@ToString |
|
||||
public final class PluginCallbackMessage<V> { |
|
||||
@Getter |
|
||||
private final PluginCallback<V> callback; |
|
||||
@Getter |
|
||||
private final boolean success; |
|
||||
@Getter |
|
||||
private final V v; |
|
||||
@Getter |
|
||||
private final Exception e; |
|
||||
|
|
||||
public static <V> PluginCallbackMessage<V> onSuccess(PluginCallback<V> callback, V data) { |
|
||||
return new PluginCallbackMessage<V>(true, callback, data, null); |
|
||||
} |
|
||||
|
|
||||
public static <V> PluginCallbackMessage<V> onError(PluginCallback<V> callback, Exception e) { |
|
||||
return new PluginCallbackMessage<V>(false, callback, null, e); |
|
||||
} |
|
||||
|
|
||||
private PluginCallbackMessage(boolean success, PluginCallback<V> callback, V v, Exception e) { |
|
||||
this.success = success; |
|
||||
this.callback = callback; |
|
||||
this.v = v; |
|
||||
this.e = e; |
|
||||
} |
|
||||
} |
|
||||
@ -1,546 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import akka.actor.ActorRef; |
|
||||
import com.google.common.base.Function; |
|
||||
import com.google.common.util.concurrent.FutureCallback; |
|
||||
import com.google.common.util.concurrent.Futures; |
|
||||
import com.google.common.util.concurrent.ListenableFuture; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.thingsboard.server.common.data.Customer; |
|
||||
import org.thingsboard.server.common.data.Device; |
|
||||
import org.thingsboard.server.common.data.EntityType; |
|
||||
import org.thingsboard.server.common.data.Tenant; |
|
||||
import org.thingsboard.server.common.data.asset.Asset; |
|
||||
import org.thingsboard.server.common.data.audit.ActionType; |
|
||||
import org.thingsboard.server.common.data.id.*; |
|
||||
import org.thingsboard.server.common.data.kv.AttributeKey; |
|
||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|
||||
import org.thingsboard.server.common.data.kv.TsKvEntry; |
|
||||
import org.thingsboard.server.common.data.kv.TsKvQuery; |
|
||||
import org.thingsboard.server.common.data.page.TextPageLink; |
|
||||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|
||||
import org.thingsboard.server.common.data.relation.EntityRelation; |
|
||||
import org.thingsboard.server.common.data.relation.RelationTypeGroup; |
|
||||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|
||||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; |
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginCallback; |
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginContext; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.*; |
|
||||
import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; |
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|
||||
|
|
||||
import javax.annotation.Nullable; |
|
||||
import java.io.IOException; |
|
||||
import java.util.*; |
|
||||
import java.util.concurrent.Executor; |
|
||||
import java.util.concurrent.Executors; |
|
||||
import java.util.stream.Collectors; |
|
||||
|
|
||||
@Slf4j |
|
||||
public final class PluginProcessingContext implements PluginContext { |
|
||||
|
|
||||
private static final Executor executor = Executors.newSingleThreadExecutor(); |
|
||||
public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!"; |
|
||||
public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; |
|
||||
public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!"; |
|
||||
|
|
||||
private final SharedPluginProcessingContext pluginCtx; |
|
||||
private final Optional<PluginApiCallSecurityContext> securityCtx; |
|
||||
|
|
||||
public PluginProcessingContext(SharedPluginProcessingContext pluginCtx, PluginApiCallSecurityContext securityCtx) { |
|
||||
super(); |
|
||||
this.pluginCtx = pluginCtx; |
|
||||
this.securityCtx = Optional.ofNullable(securityCtx); |
|
||||
} |
|
||||
|
|
||||
public void persistError(String method, Exception e) { |
|
||||
pluginCtx.persistError(method, e); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void sendPluginRpcMsg(RpcMsg msg) { |
|
||||
this.pluginCtx.rpcService.tell(new PluginRpcMsg(pluginCtx.tenantId, pluginCtx.pluginId, msg)); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void send(PluginWebsocketMsg<?> wsMsg) throws IOException { |
|
||||
pluginCtx.msgEndpoint.send(wsMsg); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void close(PluginWebsocketSessionRef sessionRef) throws IOException { |
|
||||
pluginCtx.msgEndpoint.close(sessionRef); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void saveAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<AttributeKvEntry> attributes, final PluginCallback<Void> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<Void>> futures = pluginCtx.attributesService.save(entityId, scope, attributes); |
|
||||
Futures.addCallback(futures, getListCallback(callback, v -> { |
|
||||
if (entityId.getEntityType() == EntityType.DEVICE) { |
|
||||
onDeviceAttributesChanged(tenantId, new DeviceId(entityId.getId()), scope, attributes); |
|
||||
} |
|
||||
return null; |
|
||||
}), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void removeAttributes(final TenantId tenantId, final EntityId entityId, final String scope, final List<String> keys, final PluginCallback<Void> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<Void>> futures = pluginCtx.attributesService.removeAll(entityId, scope, keys); |
|
||||
Futures.addCallback(futures, getCallback(callback, v -> null), executor); |
|
||||
if (entityId.getEntityType() == EntityType.DEVICE) { |
|
||||
onDeviceAttributesDeleted(tenantId, new DeviceId(entityId.getId()), keys.stream().map(key -> new AttributeKey(scope, key)).collect(Collectors.toSet())); |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void loadAttribute(EntityId entityId, String attributeType, String attributeKey, final PluginCallback<Optional<AttributeKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<Optional<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKey); |
|
||||
Futures.addCallback(future, getCallback(callback, v -> v), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void loadAttributes(EntityId entityId, String attributeType, Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.find(entityId, attributeType, attributeKeys); |
|
||||
Futures.addCallback(future, getCallback(callback, v -> v), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void loadAttributes(EntityId entityId, String attributeType, PluginCallback<List<AttributeKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<AttributeKvEntry>> future = pluginCtx.attributesService.findAll(entityId, attributeType); |
|
||||
Futures.addCallback(future, getCallback(callback, v -> v), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final PluginCallback<List<AttributeKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); |
|
||||
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.findAll(entityId, attributeType))); |
|
||||
convertFuturesAndAddCallback(callback, futures); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void loadAttributes(final EntityId entityId, final Collection<String> attributeTypes, final Collection<String> attributeKeys, final PluginCallback<List<AttributeKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
List<ListenableFuture<List<AttributeKvEntry>>> futures = new ArrayList<>(); |
|
||||
attributeTypes.forEach(attributeType -> futures.add(pluginCtx.attributesService.find(entityId, attributeType, attributeKeys))); |
|
||||
convertFuturesAndAddCallback(callback, futures); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void saveTsData(final EntityId entityId, final TsKvEntry entry, final PluginCallback<Void> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<Void>> rsListFuture = pluginCtx.tsService.save(entityId, entry); |
|
||||
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void saveTsData(final EntityId entityId, final List<TsKvEntry> entries, final PluginCallback<Void> callback) { |
|
||||
saveTsData(entityId, entries, 0L, callback); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void saveTsData(final EntityId entityId, final List<TsKvEntry> entries, long ttl, final PluginCallback<Void> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<Void>> rsListFuture = pluginCtx.tsService.save(entityId, entries, ttl); |
|
||||
Futures.addCallback(rsListFuture, getListCallback(callback, v -> null), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
@Override |
|
||||
public void loadTimeseries(final EntityId entityId, final List<TsKvQuery> queries, final PluginCallback<List<TsKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAll(entityId, queries); |
|
||||
Futures.addCallback(future, getCallback(callback, v -> v), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void loadLatestTimeseries(final EntityId entityId, final PluginCallback<List<TsKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<TsKvEntry>> future = pluginCtx.tsService.findAllLatest(entityId); |
|
||||
Futures.addCallback(future, getCallback(callback, v -> v), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void logAttributesUpdated(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, |
|
||||
List<AttributeKvEntry> attributes, Exception e) { |
|
||||
pluginCtx.auditLogService.logEntityAction( |
|
||||
ctx.getTenantId(), |
|
||||
ctx.getCustomerId(), |
|
||||
ctx.getUserId(), |
|
||||
ctx.getUserName(), |
|
||||
(UUIDBased & EntityId)entityId, |
|
||||
null, |
|
||||
ActionType.ATTRIBUTES_UPDATED, |
|
||||
e, |
|
||||
attributeType, |
|
||||
attributes); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void logAttributesDeleted(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) { |
|
||||
pluginCtx.auditLogService.logEntityAction( |
|
||||
ctx.getTenantId(), |
|
||||
ctx.getCustomerId(), |
|
||||
ctx.getUserId(), |
|
||||
ctx.getUserName(), |
|
||||
(UUIDBased & EntityId)entityId, |
|
||||
null, |
|
||||
ActionType.ATTRIBUTES_DELETED, |
|
||||
e, |
|
||||
attributeType, |
|
||||
keys); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void logAttributesRead(PluginApiCallSecurityContext ctx, EntityId entityId, String attributeType, List<String> keys, Exception e) { |
|
||||
pluginCtx.auditLogService.logEntityAction( |
|
||||
ctx.getTenantId(), |
|
||||
ctx.getCustomerId(), |
|
||||
ctx.getUserId(), |
|
||||
ctx.getUserName(), |
|
||||
(UUIDBased & EntityId)entityId, |
|
||||
null, |
|
||||
ActionType.ATTRIBUTES_READ, |
|
||||
e, |
|
||||
attributeType, |
|
||||
keys); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void loadLatestTimeseries(final EntityId entityId, final Collection<String> keys, final PluginCallback<List<TsKvEntry>> callback) { |
|
||||
validate(entityId, new ValidationCallback(callback, ctx -> { |
|
||||
ListenableFuture<List<TsKvEntry>> rsListFuture = pluginCtx.tsService.findLatest(entityId, keys); |
|
||||
Futures.addCallback(rsListFuture, getCallback(callback, v -> v), executor); |
|
||||
})); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void reply(PluginToRuleMsg<?> msg) { |
|
||||
pluginCtx.parentActor.tell(msg, ActorRef.noSender()); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public PluginId getPluginId() { |
|
||||
return pluginCtx.pluginId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Optional<PluginApiCallSecurityContext> getSecurityCtx() { |
|
||||
return securityCtx; |
|
||||
} |
|
||||
|
|
||||
private void onDeviceAttributesDeleted(TenantId tenantId, DeviceId deviceId, Set<AttributeKey> keys) { |
|
||||
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onDelete(tenantId, deviceId, keys)); |
|
||||
} |
|
||||
|
|
||||
private void onDeviceAttributesChanged(TenantId tenantId, DeviceId deviceId, String scope, List<AttributeKvEntry> values) { |
|
||||
pluginCtx.toDeviceActor(DeviceAttributesEventNotificationMsg.onUpdate(tenantId, deviceId, scope, values)); |
|
||||
} |
|
||||
|
|
||||
private <T, R> FutureCallback<List<T>> getListCallback(final PluginCallback<R> callback, Function<List<T>, R> transformer) { |
|
||||
return new FutureCallback<List<T>>() { |
|
||||
@Override |
|
||||
public void onSuccess(@Nullable List<T> result) { |
|
||||
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender()); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onFailure(Throwable t) { |
|
||||
if (t instanceof Exception) { |
|
||||
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender()); |
|
||||
} else { |
|
||||
log.error("Critical error: {}", t.getMessage(), t); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
private <T, R> FutureCallback<R> getCallback(final PluginCallback<T> callback, Function<R, T> transformer) { |
|
||||
return new FutureCallback<R>() { |
|
||||
@Override |
|
||||
public void onSuccess(@Nullable R result) { |
|
||||
try { |
|
||||
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, transformer.apply(result)), ActorRef.noSender()); |
|
||||
} catch (Exception e) { |
|
||||
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, e), ActorRef.noSender()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onFailure(Throwable t) { |
|
||||
if (t instanceof Exception) { |
|
||||
pluginCtx.self().tell(PluginCallbackMessage.onError(callback, (Exception) t), ActorRef.noSender()); |
|
||||
} else { |
|
||||
log.error("Critical error: {}", t.getMessage(), t); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void checkAccess(DeviceId deviceId, PluginCallback<Void> callback) { |
|
||||
validate(deviceId, new ValidationCallback(callback, ctx -> callback.onSuccess(ctx, null))); |
|
||||
} |
|
||||
|
|
||||
private void validate(EntityId entityId, ValidationCallback callback) { |
|
||||
if (securityCtx.isPresent()) { |
|
||||
final PluginApiCallSecurityContext ctx = securityCtx.get(); |
|
||||
switch (entityId.getEntityType()) { |
|
||||
case DEVICE: |
|
||||
validateDevice(ctx, entityId, callback); |
|
||||
return; |
|
||||
case ASSET: |
|
||||
validateAsset(ctx, entityId, callback); |
|
||||
return; |
|
||||
case RULE: |
|
||||
validateRule(ctx, entityId, callback); |
|
||||
return; |
|
||||
case PLUGIN: |
|
||||
validatePlugin(ctx, entityId, callback); |
|
||||
return; |
|
||||
case CUSTOMER: |
|
||||
validateCustomer(ctx, entityId, callback); |
|
||||
return; |
|
||||
case TENANT: |
|
||||
validateTenant(ctx, entityId, callback); |
|
||||
return; |
|
||||
default: |
|
||||
//TODO: add support of other entities
|
|
||||
throw new IllegalStateException("Not Implemented!"); |
|
||||
} |
|
||||
} else { |
|
||||
callback.onSuccess(this, ValidationResult.ok()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void validateDevice(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { |
|
||||
if (ctx.isSystemAdmin()) { |
|
||||
callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); |
|
||||
} else { |
|
||||
ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId())); |
|
||||
Futures.addCallback(deviceFuture, getCallback(callback, device -> { |
|
||||
if (device == null) { |
|
||||
return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND); |
|
||||
} else { |
|
||||
if (!device.getTenantId().equals(ctx.getTenantId())) { |
|
||||
return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!"); |
|
||||
} else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) { |
|
||||
return ValidationResult.accessDenied("Device doesn't belong to the current Customer!"); |
|
||||
} else { |
|
||||
return ValidationResult.ok(); |
|
||||
} |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void validateAsset(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { |
|
||||
if (ctx.isSystemAdmin()) { |
|
||||
callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); |
|
||||
} else { |
|
||||
ListenableFuture<Asset> assetFuture = pluginCtx.assetService.findAssetByIdAsync(new AssetId(entityId.getId())); |
|
||||
Futures.addCallback(assetFuture, getCallback(callback, asset -> { |
|
||||
if (asset == null) { |
|
||||
return ValidationResult.entityNotFound("Asset with requested id wasn't found!"); |
|
||||
} else { |
|
||||
if (!asset.getTenantId().equals(ctx.getTenantId())) { |
|
||||
return ValidationResult.accessDenied("Asset doesn't belong to the current Tenant!"); |
|
||||
} else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) { |
|
||||
return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!"); |
|
||||
} else { |
|
||||
return ValidationResult.ok(); |
|
||||
} |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void validateRule(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { |
|
||||
if (ctx.isCustomerUser()) { |
|
||||
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); |
|
||||
} else { |
|
||||
ListenableFuture<RuleMetaData> ruleFuture = pluginCtx.ruleService.findRuleByIdAsync(new RuleId(entityId.getId())); |
|
||||
Futures.addCallback(ruleFuture, getCallback(callback, rule -> { |
|
||||
if (rule == null) { |
|
||||
return ValidationResult.entityNotFound("Rule with requested id wasn't found!"); |
|
||||
} else { |
|
||||
if (ctx.isTenantAdmin() && !rule.getTenantId().equals(ctx.getTenantId())) { |
|
||||
return ValidationResult.accessDenied("Rule doesn't belong to the current Tenant!"); |
|
||||
} else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) { |
|
||||
return ValidationResult.accessDenied("Rule is not in system scope!"); |
|
||||
} else { |
|
||||
return ValidationResult.ok(); |
|
||||
} |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void validatePlugin(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { |
|
||||
if (ctx.isCustomerUser()) { |
|
||||
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); |
|
||||
} else { |
|
||||
ListenableFuture<PluginMetaData> pluginFuture = pluginCtx.pluginService.findPluginByIdAsync(new PluginId(entityId.getId())); |
|
||||
Futures.addCallback(pluginFuture, getCallback(callback, plugin -> { |
|
||||
if (plugin == null) { |
|
||||
return ValidationResult.entityNotFound("Plugin with requested id wasn't found!"); |
|
||||
} else { |
|
||||
if (ctx.isTenantAdmin() && !plugin.getTenantId().equals(ctx.getTenantId())) { |
|
||||
return ValidationResult.accessDenied("Plugin doesn't belong to the current Tenant!"); |
|
||||
} else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) { |
|
||||
return ValidationResult.accessDenied("Plugin is not in system scope!"); |
|
||||
} else { |
|
||||
return ValidationResult.ok(); |
|
||||
} |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void validateCustomer(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { |
|
||||
if (ctx.isSystemAdmin()) { |
|
||||
callback.onSuccess(this, ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); |
|
||||
} else { |
|
||||
ListenableFuture<Customer> customerFuture = pluginCtx.customerService.findCustomerByIdAsync(new CustomerId(entityId.getId())); |
|
||||
Futures.addCallback(customerFuture, getCallback(callback, customer -> { |
|
||||
if (customer == null) { |
|
||||
return ValidationResult.entityNotFound("Customer with requested id wasn't found!"); |
|
||||
} else { |
|
||||
if (!customer.getTenantId().equals(ctx.getTenantId())) { |
|
||||
return ValidationResult.accessDenied("Customer doesn't belong to the current Tenant!"); |
|
||||
} else if (ctx.isCustomerUser() && !customer.getId().equals(ctx.getCustomerId())) { |
|
||||
return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!"); |
|
||||
} else { |
|
||||
return ValidationResult.ok(); |
|
||||
} |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void validateTenant(final PluginApiCallSecurityContext ctx, EntityId entityId, ValidationCallback callback) { |
|
||||
if (ctx.isCustomerUser()) { |
|
||||
callback.onSuccess(this, ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); |
|
||||
} else if (ctx.isSystemAdmin()) { |
|
||||
callback.onSuccess(this, ValidationResult.ok()); |
|
||||
} else { |
|
||||
ListenableFuture<Tenant> tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId())); |
|
||||
Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { |
|
||||
if (tenant == null) { |
|
||||
return ValidationResult.entityNotFound("Tenant with requested id wasn't found!"); |
|
||||
} else if (!tenant.getId().equals(ctx.getTenantId())) { |
|
||||
return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!"); |
|
||||
} else { |
|
||||
return ValidationResult.ok(); |
|
||||
} |
|
||||
})); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<List<EntityRelation>> findByFromAndType(EntityId from, String relationType) { |
|
||||
return this.pluginCtx.relationService.findByFromAndTypeAsync(from, relationType, RelationTypeGroup.COMMON); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<List<EntityRelation>> findByToAndType(EntityId from, String relationType) { |
|
||||
return this.pluginCtx.relationService.findByToAndTypeAsync(from, relationType, RelationTypeGroup.COMMON); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Optional<ServerAddress> resolve(EntityId entityId) { |
|
||||
return pluginCtx.routingService.resolveById(entityId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void getDevice(DeviceId deviceId, PluginCallback<Device> callback) { |
|
||||
ListenableFuture<Device> deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(deviceId); |
|
||||
Futures.addCallback(deviceFuture, getCallback(callback, v -> v)); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void getCustomerDevices(TenantId tenantId, CustomerId customerId, int limit, PluginCallback<List<Device>> callback) { |
|
||||
//TODO: add caching here with async api.
|
|
||||
List<Device> devices = pluginCtx.deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, new TextPageLink(limit)).getData(); |
|
||||
pluginCtx.self().tell(PluginCallbackMessage.onSuccess(callback, devices), ActorRef.noSender()); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void sendRpcRequest(ToDeviceRpcRequest msg) { |
|
||||
pluginCtx.sendRpcRequest(msg); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void logRpcRequest(PluginApiCallSecurityContext ctx, DeviceId deviceId, ToDeviceRpcRequestBody body, boolean oneWay, Optional<RpcError> rpcError, Exception e) { |
|
||||
String rpcErrorStr = ""; |
|
||||
if (rpcError.isPresent()) { |
|
||||
rpcErrorStr = "RPC Error: " + rpcError.get().name(); |
|
||||
} |
|
||||
String method = body.getMethod(); |
|
||||
String params = body.getParams(); |
|
||||
pluginCtx.auditLogService.logEntityAction( |
|
||||
ctx.getTenantId(), |
|
||||
ctx.getCustomerId(), |
|
||||
ctx.getUserId(), |
|
||||
ctx.getUserName(), |
|
||||
deviceId, |
|
||||
null, |
|
||||
ActionType.RPC_CALL, |
|
||||
e, |
|
||||
rpcErrorStr, |
|
||||
new Boolean(oneWay), |
|
||||
method, |
|
||||
params); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void scheduleTimeoutMsg(TimeoutMsg msg) { |
|
||||
pluginCtx.scheduleTimeoutMsg(msg); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private void convertFuturesAndAddCallback(PluginCallback<List<AttributeKvEntry>> callback, List<ListenableFuture<List<AttributeKvEntry>>> futures) { |
|
||||
ListenableFuture<List<AttributeKvEntry>> future = Futures.transform(Futures.successfulAsList(futures), |
|
||||
(Function<? super List<List<AttributeKvEntry>>, ? extends List<AttributeKvEntry>>) input -> { |
|
||||
List<AttributeKvEntry> result = new ArrayList<>(); |
|
||||
input.forEach(r -> result.addAll(r)); |
|
||||
return result; |
|
||||
}, executor); |
|
||||
Futures.addCallback(future, getCallback(callback, v -> v), executor); |
|
||||
} |
|
||||
} |
|
||||
@ -1,30 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import org.thingsboard.server.actors.shared.ActorTerminationMsg; |
|
||||
import org.thingsboard.server.common.data.id.PluginId; |
|
||||
import org.thingsboard.server.common.data.id.SessionId; |
|
||||
|
|
||||
/** |
|
||||
* @author Andrew Shvayka |
|
||||
*/ |
|
||||
public class PluginTerminationMsg extends ActorTerminationMsg<PluginId> { |
|
||||
|
|
||||
public PluginTerminationMsg(PluginId id) { |
|
||||
super(id); |
|
||||
} |
|
||||
} |
|
||||
@ -1,66 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import org.thingsboard.server.common.data.id.PluginId; |
|
||||
import org.thingsboard.server.common.data.id.RuleId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.msg.aware.RuleAwareMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; |
|
||||
|
|
||||
public class RuleToPluginMsgWrapper implements ToPluginActorMsg, RuleAwareMsg { |
|
||||
|
|
||||
private final TenantId pluginTenantId; |
|
||||
private final PluginId pluginId; |
|
||||
private final TenantId ruleTenantId; |
|
||||
private final RuleId ruleId; |
|
||||
private final RuleToPluginMsg<?> msg; |
|
||||
|
|
||||
public RuleToPluginMsgWrapper(TenantId pluginTenantId, PluginId pluginId, TenantId ruleTenantId, RuleId ruleId, RuleToPluginMsg<?> msg) { |
|
||||
super(); |
|
||||
this.pluginTenantId = pluginTenantId; |
|
||||
this.pluginId = pluginId; |
|
||||
this.ruleTenantId = ruleTenantId; |
|
||||
this.ruleId = ruleId; |
|
||||
this.msg = msg; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public TenantId getPluginTenantId() { |
|
||||
return pluginTenantId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public PluginId getPluginId() { |
|
||||
return pluginId; |
|
||||
} |
|
||||
|
|
||||
public TenantId getRuleTenantId() { |
|
||||
return ruleTenantId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public RuleId getRuleId() { |
|
||||
return ruleId; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
public RuleToPluginMsg getMsg() { |
|
||||
return msg; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,141 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import akka.actor.ActorRef; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.common.data.Device; |
|
||||
import org.thingsboard.server.common.data.id.DeviceId; |
|
||||
import org.thingsboard.server.common.data.id.EntityId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.msg.cluster.ServerAddress; |
|
||||
import org.thingsboard.server.controller.plugin.PluginWebSocketMsgEndpoint; |
|
||||
import org.thingsboard.server.common.data.id.PluginId; |
|
||||
import org.thingsboard.server.dao.asset.AssetService; |
|
||||
import org.thingsboard.server.dao.attributes.AttributesService; |
|
||||
import org.thingsboard.server.dao.audit.AuditLogService; |
|
||||
import org.thingsboard.server.dao.customer.CustomerService; |
|
||||
import org.thingsboard.server.dao.device.DeviceService; |
|
||||
import org.thingsboard.server.dao.plugin.PluginService; |
|
||||
import org.thingsboard.server.dao.relation.RelationService; |
|
||||
import org.thingsboard.server.dao.rule.RuleService; |
|
||||
import org.thingsboard.server.dao.tenant.TenantService; |
|
||||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|
||||
import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; |
|
||||
import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; |
|
||||
import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; |
|
||||
import scala.concurrent.duration.Duration; |
|
||||
|
|
||||
import java.util.Optional; |
|
||||
import java.util.concurrent.TimeUnit; |
|
||||
import java.util.function.BiConsumer; |
|
||||
|
|
||||
@Slf4j |
|
||||
public final class SharedPluginProcessingContext { |
|
||||
final ActorRef parentActor; |
|
||||
final ActorRef currentActor; |
|
||||
final ActorSystemContext systemContext; |
|
||||
final PluginWebSocketMsgEndpoint msgEndpoint; |
|
||||
final AssetService assetService; |
|
||||
final DeviceService deviceService; |
|
||||
final RuleService ruleService; |
|
||||
final PluginService pluginService; |
|
||||
final CustomerService customerService; |
|
||||
final TenantService tenantService; |
|
||||
final TimeseriesService tsService; |
|
||||
final AttributesService attributesService; |
|
||||
final ClusterRpcService rpcService; |
|
||||
final ClusterRoutingService routingService; |
|
||||
final RelationService relationService; |
|
||||
final AuditLogService auditLogService; |
|
||||
final PluginId pluginId; |
|
||||
final TenantId tenantId; |
|
||||
|
|
||||
public SharedPluginProcessingContext(ActorSystemContext sysContext, TenantId tenantId, PluginId pluginId, |
|
||||
ActorRef parentActor, ActorRef self) { |
|
||||
super(); |
|
||||
this.tenantId = tenantId; |
|
||||
this.pluginId = pluginId; |
|
||||
this.parentActor = parentActor; |
|
||||
this.currentActor = self; |
|
||||
this.systemContext = sysContext; |
|
||||
this.msgEndpoint = sysContext.getWsMsgEndpoint(); |
|
||||
this.tsService = sysContext.getTsService(); |
|
||||
this.attributesService = sysContext.getAttributesService(); |
|
||||
this.assetService = sysContext.getAssetService(); |
|
||||
this.deviceService = sysContext.getDeviceService(); |
|
||||
this.rpcService = sysContext.getRpcService(); |
|
||||
this.routingService = sysContext.getRoutingService(); |
|
||||
this.ruleService = sysContext.getRuleService(); |
|
||||
this.pluginService = sysContext.getPluginService(); |
|
||||
this.customerService = sysContext.getCustomerService(); |
|
||||
this.tenantService = sysContext.getTenantService(); |
|
||||
this.relationService = sysContext.getRelationService(); |
|
||||
this.auditLogService = sysContext.getAuditLogService(); |
|
||||
} |
|
||||
|
|
||||
public PluginId getPluginId() { |
|
||||
return pluginId; |
|
||||
} |
|
||||
|
|
||||
public TenantId getPluginTenantId() { |
|
||||
return tenantId; |
|
||||
} |
|
||||
|
|
||||
public void toDeviceActor(DeviceAttributesEventNotificationMsg msg) { |
|
||||
forward(msg.getDeviceId(), msg, rpcService::tell); |
|
||||
} |
|
||||
|
|
||||
public void sendRpcRequest(ToDeviceRpcRequest msg) { |
|
||||
log.trace("[{}] Forwarding msg {} to device actor!", pluginId, msg); |
|
||||
ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg); |
|
||||
forward(msg.getDeviceId(), rpcMsg, rpcService::tell); |
|
||||
} |
|
||||
|
|
||||
private <T> void forward(DeviceId deviceId, T msg, BiConsumer<ServerAddress, T> rpcFunction) { |
|
||||
Optional<ServerAddress> instance = routingService.resolveById(deviceId); |
|
||||
if (instance.isPresent()) { |
|
||||
log.trace("[{}] Forwarding msg {} to remote device actor!", pluginId, msg); |
|
||||
rpcFunction.accept(instance.get(), msg); |
|
||||
} else { |
|
||||
log.trace("[{}] Forwarding msg {} to local device actor!", pluginId, msg); |
|
||||
parentActor.tell(msg, ActorRef.noSender()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public void scheduleTimeoutMsg(TimeoutMsg msg) { |
|
||||
log.debug("Scheduling msg {} with delay {} ms", msg, msg.getTimeout()); |
|
||||
systemContext.getScheduler().scheduleOnce( |
|
||||
Duration.create(msg.getTimeout(), TimeUnit.MILLISECONDS), |
|
||||
currentActor, |
|
||||
msg, |
|
||||
systemContext.getActorSystem().dispatcher(), |
|
||||
ActorRef.noSender()); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
public void persistError(String method, Exception e) { |
|
||||
systemContext.persistError(tenantId, pluginId, method, e); |
|
||||
} |
|
||||
|
|
||||
public ActorRef self() { |
|
||||
return currentActor; |
|
||||
} |
|
||||
} |
|
||||
@ -1,48 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.plugin; |
|
||||
|
|
||||
import lombok.AllArgsConstructor; |
|
||||
import lombok.Data; |
|
||||
|
|
||||
@Data |
|
||||
@AllArgsConstructor |
|
||||
public class ValidationResult { |
|
||||
|
|
||||
private final ValidationResultCode resultCode; |
|
||||
private final String message; |
|
||||
|
|
||||
public static ValidationResult ok() { |
|
||||
return new ValidationResult(ValidationResultCode.OK, "Ok"); |
|
||||
} |
|
||||
|
|
||||
public static ValidationResult accessDenied(String message) { |
|
||||
return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message); |
|
||||
} |
|
||||
|
|
||||
public static ValidationResult entityNotFound(String message) { |
|
||||
return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message); |
|
||||
} |
|
||||
|
|
||||
public static ValidationResult unauthorized(String message) { |
|
||||
return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message); |
|
||||
} |
|
||||
|
|
||||
public static ValidationResult internalError(String message) { |
|
||||
return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,117 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.rule; |
|
||||
|
|
||||
import akka.actor.ActorRef; |
|
||||
import org.thingsboard.server.common.msg.core.RuleEngineError; |
|
||||
import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg; |
|
||||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|
||||
import org.thingsboard.server.extensions.api.device.DeviceAttributes; |
|
||||
import org.thingsboard.server.extensions.api.device.DeviceMetaData; |
|
||||
|
|
||||
public class ChainProcessingContext { |
|
||||
|
|
||||
private final ChainProcessingMetaData md; |
|
||||
private final int index; |
|
||||
private final RuleEngineError error; |
|
||||
private ToDeviceMsg response; |
|
||||
|
|
||||
|
|
||||
public ChainProcessingContext(ChainProcessingMetaData md) { |
|
||||
super(); |
|
||||
this.md = md; |
|
||||
this.index = 0; |
|
||||
this.error = RuleEngineError.NO_RULES; |
|
||||
} |
|
||||
|
|
||||
private ChainProcessingContext(ChainProcessingContext other, int indexOffset, RuleEngineError error) { |
|
||||
super(); |
|
||||
this.md = other.md; |
|
||||
this.index = other.index + indexOffset; |
|
||||
this.error = error; |
|
||||
this.response = other.response; |
|
||||
|
|
||||
if (this.index < 0 || this.index >= this.md.chain.size()) { |
|
||||
throw new IllegalArgumentException("Can't apply offset " + indexOffset + " to the chain!"); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public ActorRef getDeviceActor() { |
|
||||
return md.originator; |
|
||||
} |
|
||||
|
|
||||
public ActorRef getCurrentActor() { |
|
||||
return md.chain.getRuleActorMd(index).getActorRef(); |
|
||||
} |
|
||||
|
|
||||
public boolean hasNext() { |
|
||||
return (getChainLength() - 1) > index; |
|
||||
} |
|
||||
|
|
||||
public boolean isFailure() { |
|
||||
return (error != null && error.isCritical()) || (response != null && !response.isSuccess()); |
|
||||
} |
|
||||
|
|
||||
public ChainProcessingContext getNext() { |
|
||||
return new ChainProcessingContext(this, 1, this.error); |
|
||||
} |
|
||||
|
|
||||
public ChainProcessingContext withError(RuleEngineError error) { |
|
||||
if (error != null && (this.error == null || this.error.getPriority() < error.getPriority())) { |
|
||||
return new ChainProcessingContext(this, 0, error); |
|
||||
} else { |
|
||||
return this; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public int getChainLength() { |
|
||||
return md.chain.size(); |
|
||||
} |
|
||||
|
|
||||
public ToDeviceActorMsg getInMsg() { |
|
||||
return md.inMsg; |
|
||||
} |
|
||||
|
|
||||
public DeviceMetaData getDeviceMetaData() { |
|
||||
return md.deviceMetaData; |
|
||||
} |
|
||||
|
|
||||
public String getDeviceName() { |
|
||||
return md.deviceMetaData.getDeviceName(); |
|
||||
} |
|
||||
|
|
||||
public String getDeviceType() { |
|
||||
return md.deviceMetaData.getDeviceType(); |
|
||||
} |
|
||||
|
|
||||
public DeviceAttributes getAttributes() { |
|
||||
return md.deviceMetaData.getDeviceAttributes(); |
|
||||
} |
|
||||
|
|
||||
public ToDeviceMsg getResponse() { |
|
||||
return response; |
|
||||
} |
|
||||
|
|
||||
public void mergeResponse(ToDeviceMsg response) { |
|
||||
// TODO add merge logic
|
|
||||
this.response = response; |
|
||||
} |
|
||||
|
|
||||
public RuleEngineErrorMsg getError() { |
|
||||
return new RuleEngineErrorMsg(md.inMsg.getPayload().getMsgType(), error); |
|
||||
} |
|
||||
} |
|
||||
@ -1,41 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.rule; |
|
||||
|
|
||||
import akka.actor.ActorRef; |
|
||||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|
||||
import org.thingsboard.server.extensions.api.device.DeviceMetaData; |
|
||||
|
|
||||
/** |
|
||||
* Immutable part of chain processing data; |
|
||||
* |
|
||||
* @author ashvayka |
|
||||
*/ |
|
||||
public final class ChainProcessingMetaData { |
|
||||
|
|
||||
final RuleActorChain chain; |
|
||||
final ToDeviceActorMsg inMsg; |
|
||||
final ActorRef originator; |
|
||||
final DeviceMetaData deviceMetaData; |
|
||||
|
|
||||
public ChainProcessingMetaData(RuleActorChain chain, ToDeviceActorMsg inMsg, DeviceMetaData deviceMetaData, ActorRef originator) { |
|
||||
super(); |
|
||||
this.chain = chain; |
|
||||
this.inMsg = inMsg; |
|
||||
this.originator = originator; |
|
||||
this.deviceMetaData = deviceMetaData; |
|
||||
} |
|
||||
} |
|
||||
@ -1,43 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.rule; |
|
||||
|
|
||||
public class ComplexRuleActorChain implements RuleActorChain { |
|
||||
|
|
||||
private final RuleActorChain systemChain; |
|
||||
private final RuleActorChain tenantChain; |
|
||||
|
|
||||
public ComplexRuleActorChain(RuleActorChain systemChain, RuleActorChain tenantChain) { |
|
||||
super(); |
|
||||
this.systemChain = systemChain; |
|
||||
this.tenantChain = tenantChain; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int size() { |
|
||||
return systemChain.size() + tenantChain.size(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public RuleActorMetaData getRuleActorMd(int index) { |
|
||||
if (index < systemChain.size()) { |
|
||||
return systemChain.getRuleActorMd(index); |
|
||||
} else { |
|
||||
return tenantChain.getRuleActorMd(index - systemChain.size()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,90 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.rule; |
|
||||
|
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.service.ComponentActor; |
|
||||
import org.thingsboard.server.actors.service.ContextBasedCreator; |
|
||||
import org.thingsboard.server.actors.stats.StatsPersistTick; |
|
||||
import org.thingsboard.server.common.data.id.RuleId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; |
|
||||
|
|
||||
public class RuleActor extends ComponentActor<RuleId, RuleActorMessageProcessor> { |
|
||||
|
|
||||
private RuleActor(ActorSystemContext systemContext, TenantId tenantId, RuleId ruleId) { |
|
||||
super(systemContext, tenantId, ruleId); |
|
||||
setProcessor(new RuleActorMessageProcessor(tenantId, ruleId, systemContext, logger)); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onReceive(Object msg) throws Exception { |
|
||||
logger.debug("[{}] Received message: {}", id, msg); |
|
||||
if (msg instanceof RuleProcessingMsg) { |
|
||||
try { |
|
||||
processor.onRuleProcessingMsg(context(), (RuleProcessingMsg) msg); |
|
||||
increaseMessagesProcessedCount(); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onDeviceMsg", e); |
|
||||
} |
|
||||
} else if (msg instanceof PluginToRuleMsg<?>) { |
|
||||
try { |
|
||||
processor.onPluginMsg(context(), (PluginToRuleMsg<?>) msg); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onPluginMsg", e); |
|
||||
} |
|
||||
} else if (msg instanceof ComponentLifecycleMsg) { |
|
||||
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
|
||||
} else if (msg instanceof ClusterEventMsg) { |
|
||||
onClusterEventMsg((ClusterEventMsg) msg); |
|
||||
} else if (msg instanceof RuleToPluginTimeoutMsg) { |
|
||||
try { |
|
||||
processor.onTimeoutMsg(context(), (RuleToPluginTimeoutMsg) msg); |
|
||||
} catch (Exception e) { |
|
||||
logAndPersist("onTimeoutMsg", e); |
|
||||
} |
|
||||
} else if (msg instanceof StatsPersistTick) { |
|
||||
onStatsPersistTick(id); |
|
||||
} else { |
|
||||
logger.debug("[{}][{}] Unknown msg type.", tenantId, id, msg.getClass().getName()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public static class ActorCreator extends ContextBasedCreator<RuleActor> { |
|
||||
private static final long serialVersionUID = 1L; |
|
||||
|
|
||||
private final TenantId tenantId; |
|
||||
private final RuleId ruleId; |
|
||||
|
|
||||
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleId ruleId) { |
|
||||
super(context); |
|
||||
this.tenantId = tenantId; |
|
||||
this.ruleId = ruleId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public RuleActor create() throws Exception { |
|
||||
return new RuleActor(context, tenantId, ruleId); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected long getErrorPersistFrequency() { |
|
||||
return systemContext.getRuleErrorPersistFrequency(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,354 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.rule; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.actor.ActorRef; |
|
||||
import akka.event.LoggingAdapter; |
|
||||
import com.fasterxml.jackson.core.JsonProcessingException; |
|
||||
import org.springframework.util.StringUtils; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.plugin.RuleToPluginMsgWrapper; |
|
||||
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
|
||||
import org.thingsboard.server.common.data.id.PluginId; |
|
||||
import org.thingsboard.server.common.data.id.RuleId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
|
||||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|
||||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|
||||
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
|
||||
import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; |
|
||||
import org.thingsboard.server.common.msg.core.RuleEngineError; |
|
||||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|
||||
import org.thingsboard.server.common.msg.session.ToDeviceMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.PluginAction; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.ResponsePluginToRuleMsg; |
|
||||
import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; |
|
||||
import org.thingsboard.server.extensions.api.rules.*; |
|
||||
|
|
||||
import java.util.*; |
|
||||
|
|
||||
class RuleActorMessageProcessor extends ComponentMsgProcessor<RuleId> { |
|
||||
|
|
||||
private final RuleProcessingContext ruleCtx; |
|
||||
private final Map<UUID, RuleProcessingMsg> pendingMsgMap; |
|
||||
|
|
||||
private RuleMetaData ruleMd; |
|
||||
private ComponentLifecycleState state; |
|
||||
private List<RuleFilter> filters; |
|
||||
private RuleProcessor processor; |
|
||||
private PluginAction action; |
|
||||
|
|
||||
private TenantId pluginTenantId; |
|
||||
private PluginId pluginId; |
|
||||
|
|
||||
protected RuleActorMessageProcessor(TenantId tenantId, RuleId ruleId, ActorSystemContext systemContext, LoggingAdapter logger) { |
|
||||
super(systemContext, logger, tenantId, ruleId); |
|
||||
this.pendingMsgMap = new HashMap<>(); |
|
||||
this.ruleCtx = new RuleProcessingContext(systemContext, ruleId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void start() throws Exception { |
|
||||
logger.info("[{}][{}] Starting rule actor.", entityId, tenantId); |
|
||||
ruleMd = systemContext.getRuleService().findRuleById(entityId); |
|
||||
if (ruleMd == null) { |
|
||||
throw new RuleInitializationException("Rule not found!"); |
|
||||
} |
|
||||
state = ruleMd.getState(); |
|
||||
if (state == ComponentLifecycleState.ACTIVE) { |
|
||||
logger.info("[{}] Rule is active. Going to initialize rule components.", entityId); |
|
||||
initComponent(); |
|
||||
} else { |
|
||||
logger.info("[{}] Rule is suspended. Skipping rule components initialization.", entityId); |
|
||||
} |
|
||||
|
|
||||
logger.info("[{}][{}] Started rule actor.", entityId, tenantId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void stop() throws Exception { |
|
||||
onStop(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
private void initComponent() throws RuleException { |
|
||||
try { |
|
||||
if (!ruleMd.getFilters().isArray()) { |
|
||||
throw new RuntimeException("Filters are not array!"); |
|
||||
} |
|
||||
fetchPluginInfo(); |
|
||||
initFilters(); |
|
||||
initProcessor(); |
|
||||
initAction(); |
|
||||
} catch (RuntimeException e) { |
|
||||
throw new RuleInitializationException("Unknown runtime exception!", e); |
|
||||
} catch (InstantiationException e) { |
|
||||
throw new RuleInitializationException("No default constructor for rule implementation!", e); |
|
||||
} catch (IllegalAccessException e) { |
|
||||
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); |
|
||||
} catch (ClassNotFoundException e) { |
|
||||
throw new RuleInitializationException("Rule Class not found!", e); |
|
||||
} catch (Exception e) { |
|
||||
throw new RuleException(e.getMessage(), e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void initAction() throws Exception { |
|
||||
if (ruleMd.getAction() != null && !ruleMd.getAction().isNull()) { |
|
||||
action = initComponent(ruleMd.getAction()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void initProcessor() throws Exception { |
|
||||
if (ruleMd.getProcessor() != null && !ruleMd.getProcessor().isNull()) { |
|
||||
processor = initComponent(ruleMd.getProcessor()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void initFilters() throws Exception { |
|
||||
filters = new ArrayList<>(ruleMd.getFilters().size()); |
|
||||
for (int i = 0; i < ruleMd.getFilters().size(); i++) { |
|
||||
filters.add(initComponent(ruleMd.getFilters().get(i))); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void fetchPluginInfo() { |
|
||||
if (!StringUtils.isEmpty(ruleMd.getPluginToken())) { |
|
||||
PluginMetaData pluginMd = systemContext.getPluginService().findPluginByApiToken(ruleMd.getPluginToken()); |
|
||||
pluginTenantId = pluginMd.getTenantId(); |
|
||||
pluginId = pluginMd.getId(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected void onRuleProcessingMsg(ActorContext context, RuleProcessingMsg msg) throws RuleException { |
|
||||
if (state != ComponentLifecycleState.ACTIVE) { |
|
||||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_ACTIVE_RULES); |
|
||||
return; |
|
||||
} |
|
||||
ChainProcessingContext chainCtx = msg.getCtx(); |
|
||||
ToDeviceActorMsg inMsg = chainCtx.getInMsg(); |
|
||||
|
|
||||
ruleCtx.update(inMsg, chainCtx.getDeviceMetaData()); |
|
||||
|
|
||||
logger.debug("[{}] Going to filter in msg: {}", entityId, inMsg); |
|
||||
for (RuleFilter filter : filters) { |
|
||||
if (!filter.filter(ruleCtx, inMsg)) { |
|
||||
logger.debug("[{}] In msg is NOT valid for processing by current rule: {}", entityId, inMsg); |
|
||||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_FILTERS_MATCHED); |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
RuleProcessingMetaData inMsgMd; |
|
||||
if (processor != null) { |
|
||||
logger.debug("[{}] Going to process in msg: {}", entityId, inMsg); |
|
||||
inMsgMd = processor.process(ruleCtx, inMsg); |
|
||||
} else { |
|
||||
inMsgMd = new RuleProcessingMetaData(); |
|
||||
} |
|
||||
logger.debug("[{}] Going to convert in msg: {}", entityId, inMsg); |
|
||||
if (action != null) { |
|
||||
Optional<RuleToPluginMsg<?>> ruleToPluginMsgOptional = action.convert(ruleCtx, inMsg, inMsgMd); |
|
||||
if (ruleToPluginMsgOptional.isPresent()) { |
|
||||
RuleToPluginMsg<?> ruleToPluginMsg = ruleToPluginMsgOptional.get(); |
|
||||
logger.debug("[{}] Device msg is converted to: {}", entityId, ruleToPluginMsg); |
|
||||
context.parent().tell(new RuleToPluginMsgWrapper(pluginTenantId, pluginId, tenantId, entityId, ruleToPluginMsg), context.self()); |
|
||||
if (action.isOneWayAction()) { |
|
||||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); |
|
||||
return; |
|
||||
} else { |
|
||||
pendingMsgMap.put(ruleToPluginMsg.getUid(), msg); |
|
||||
scheduleMsgWithDelay(context, new RuleToPluginTimeoutMsg(ruleToPluginMsg.getUid()), systemContext.getPluginProcessingTimeout()); |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
logger.debug("[{}] Nothing to send to plugin: {}", entityId, pluginId); |
|
||||
pushToNextRule(context, msg.getCtx(), RuleEngineError.NO_TWO_WAY_ACTIONS); |
|
||||
} |
|
||||
|
|
||||
void onPluginMsg(ActorContext context, PluginToRuleMsg<?> msg) { |
|
||||
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getUid()); |
|
||||
if (pendingMsg != null) { |
|
||||
ChainProcessingContext ctx = pendingMsg.getCtx(); |
|
||||
if (isErrorResponce(msg)) { |
|
||||
pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS); |
|
||||
} else { |
|
||||
Optional<ToDeviceMsg> ruleResponseOptional = action.convert(msg); |
|
||||
if (ruleResponseOptional.isPresent()) { |
|
||||
ctx.mergeResponse(ruleResponseOptional.get()); |
|
||||
pushToNextRule(context, ctx, null); |
|
||||
} else { |
|
||||
pushToNextRule(context, ctx, RuleEngineError.NO_RESPONSE_FROM_ACTIONS); |
|
||||
} |
|
||||
} |
|
||||
} else { |
|
||||
logger.warning("[{}] Processing timeout detected: [{}]", entityId, msg.getUid()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private boolean isErrorResponce(PluginToRuleMsg<?> msg) { |
|
||||
if (msg instanceof ResponsePluginToRuleMsg) { |
|
||||
if (((ResponsePluginToRuleMsg) msg).getPayload() instanceof BasicStatusCodeResponse) { |
|
||||
BasicStatusCodeResponse responce = (BasicStatusCodeResponse) ((ResponsePluginToRuleMsg) msg).getPayload(); |
|
||||
return !responce.isSuccess(); |
|
||||
} |
|
||||
} |
|
||||
return false; |
|
||||
} |
|
||||
|
|
||||
void onTimeoutMsg(ActorContext context, RuleToPluginTimeoutMsg msg) { |
|
||||
RuleProcessingMsg pendingMsg = pendingMsgMap.remove(msg.getMsgId()); |
|
||||
if (pendingMsg != null) { |
|
||||
logger.debug("[{}] Processing timeout detected [{}]: {}", entityId, msg.getMsgId(), pendingMsg); |
|
||||
ChainProcessingContext ctx = pendingMsg.getCtx(); |
|
||||
pushToNextRule(context, ctx, RuleEngineError.PLUGIN_TIMEOUT); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void pushToNextRule(ActorContext context, ChainProcessingContext ctx, RuleEngineError error) { |
|
||||
if (error != null) { |
|
||||
ctx = ctx.withError(error); |
|
||||
} |
|
||||
if (ctx.isFailure()) { |
|
||||
logger.debug("[{}][{}] Forwarding processing chain to device actor due to failure.", ruleMd.getId(), ctx.getInMsg().getDeviceId()); |
|
||||
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); |
|
||||
} else if (!ctx.hasNext()) { |
|
||||
logger.debug("[{}][{}] Forwarding processing chain to device actor due to end of chain.", ruleMd.getId(), ctx.getInMsg().getDeviceId()); |
|
||||
ctx.getDeviceActor().tell(new RulesProcessedMsg(ctx), ActorRef.noSender()); |
|
||||
} else { |
|
||||
logger.debug("[{}][{}] Forwarding processing chain to next rule actor.", ruleMd.getId(), ctx.getInMsg().getDeviceId()); |
|
||||
ChainProcessingContext nextTask = ctx.getNext(); |
|
||||
nextTask.getCurrentActor().tell(new RuleProcessingMsg(nextTask), context.self()); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onCreated(ActorContext context) { |
|
||||
logger.info("[{}] Going to process onCreated rule.", entityId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onUpdate(ActorContext context) throws RuleException { |
|
||||
RuleMetaData oldRuleMd = ruleMd; |
|
||||
ruleMd = systemContext.getRuleService().findRuleById(entityId); |
|
||||
logger.info("[{}] Rule configuration was updated from {} to {}.", entityId, oldRuleMd, ruleMd); |
|
||||
try { |
|
||||
fetchPluginInfo(); |
|
||||
if (filters == null || !Objects.equals(oldRuleMd.getFilters(), ruleMd.getFilters())) { |
|
||||
logger.info("[{}] Rule filters require restart due to json change from {} to {}.", |
|
||||
entityId, mapper.writeValueAsString(oldRuleMd.getFilters()), mapper.writeValueAsString(ruleMd.getFilters())); |
|
||||
stopFilters(); |
|
||||
initFilters(); |
|
||||
} |
|
||||
if (processor == null || !Objects.equals(oldRuleMd.getProcessor(), ruleMd.getProcessor())) { |
|
||||
logger.info("[{}] Rule processor require restart due to configuration change.", entityId); |
|
||||
stopProcessor(); |
|
||||
initProcessor(); |
|
||||
} |
|
||||
if (action == null || !Objects.equals(oldRuleMd.getAction(), ruleMd.getAction())) { |
|
||||
logger.info("[{}] Rule action require restart due to configuration change.", entityId); |
|
||||
stopAction(); |
|
||||
initAction(); |
|
||||
} |
|
||||
} catch (RuntimeException e) { |
|
||||
throw new RuleInitializationException("Unknown runtime exception!", e); |
|
||||
} catch (InstantiationException e) { |
|
||||
throw new RuleInitializationException("No default constructor for rule implementation!", e); |
|
||||
} catch (IllegalAccessException e) { |
|
||||
throw new RuleInitializationException("Illegal Access Exception during rule initialization!", e); |
|
||||
} catch (ClassNotFoundException e) { |
|
||||
throw new RuleInitializationException("Rule Class not found!", e); |
|
||||
} catch (JsonProcessingException e) { |
|
||||
throw new RuleInitializationException("Rule configuration is invalid!", e); |
|
||||
} catch (Exception e) { |
|
||||
throw new RuleInitializationException(e.getMessage(), e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onActivate(ActorContext context) throws Exception { |
|
||||
logger.info("[{}] Going to process onActivate rule.", entityId); |
|
||||
this.state = ComponentLifecycleState.ACTIVE; |
|
||||
if (filters != null) { |
|
||||
filters.forEach(RuleLifecycleComponent::resume); |
|
||||
if (processor != null) { |
|
||||
processor.resume(); |
|
||||
} else { |
|
||||
initProcessor(); |
|
||||
} |
|
||||
if (action != null) { |
|
||||
action.resume(); |
|
||||
} |
|
||||
logger.info("[{}] Rule resumed.", entityId); |
|
||||
} else { |
|
||||
start(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onSuspend(ActorContext context) { |
|
||||
logger.info("[{}] Going to process onSuspend rule.", entityId); |
|
||||
this.state = ComponentLifecycleState.SUSPENDED; |
|
||||
if (filters != null) { |
|
||||
filters.forEach(f -> f.suspend()); |
|
||||
} |
|
||||
if (processor != null) { |
|
||||
processor.suspend(); |
|
||||
} |
|
||||
if (action != null) { |
|
||||
action.suspend(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onStop(ActorContext context) { |
|
||||
logger.info("[{}] Going to process onStop rule.", entityId); |
|
||||
onStop(); |
|
||||
scheduleMsgWithDelay(context, new RuleTerminationMsg(entityId), systemContext.getRuleActorTerminationDelay()); |
|
||||
} |
|
||||
|
|
||||
private void onStop() { |
|
||||
this.state = ComponentLifecycleState.SUSPENDED; |
|
||||
stopFilters(); |
|
||||
stopProcessor(); |
|
||||
stopAction(); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { |
|
||||
//Do nothing
|
|
||||
} |
|
||||
|
|
||||
private void stopAction() { |
|
||||
if (action != null) { |
|
||||
action.stop(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void stopProcessor() { |
|
||||
if (processor != null) { |
|
||||
processor.stop(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void stopFilters() { |
|
||||
if (filters != null) { |
|
||||
filters.forEach(f -> f.stop()); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,107 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.rule; |
|
||||
|
|
||||
import java.util.Comparator; |
|
||||
|
|
||||
import org.thingsboard.server.common.data.id.RuleId; |
|
||||
|
|
||||
import akka.actor.ActorRef; |
|
||||
|
|
||||
public class RuleActorMetaData { |
|
||||
|
|
||||
private final RuleId ruleId; |
|
||||
private final boolean systemRule; |
|
||||
private final int weight; |
|
||||
private final ActorRef actorRef; |
|
||||
|
|
||||
public static final Comparator<RuleActorMetaData> RULE_ACTOR_MD_COMPARATOR = new Comparator<RuleActorMetaData>() { |
|
||||
|
|
||||
@Override |
|
||||
public int compare(RuleActorMetaData r1, RuleActorMetaData r2) { |
|
||||
if (r1.isSystemRule() && !r2.isSystemRule()) { |
|
||||
return 1; |
|
||||
} else if (!r1.isSystemRule() && r2.isSystemRule()) { |
|
||||
return -1; |
|
||||
} else { |
|
||||
return Integer.compare(r2.getWeight(), r1.getWeight()); |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
public static RuleActorMetaData systemRule(RuleId ruleId, int weight, ActorRef actorRef) { |
|
||||
return new RuleActorMetaData(ruleId, true, weight, actorRef); |
|
||||
} |
|
||||
|
|
||||
public static RuleActorMetaData tenantRule(RuleId ruleId, int weight, ActorRef actorRef) { |
|
||||
return new RuleActorMetaData(ruleId, false, weight, actorRef); |
|
||||
} |
|
||||
|
|
||||
private RuleActorMetaData(RuleId ruleId, boolean systemRule, int weight, ActorRef actorRef) { |
|
||||
super(); |
|
||||
this.ruleId = ruleId; |
|
||||
this.systemRule = systemRule; |
|
||||
this.weight = weight; |
|
||||
this.actorRef = actorRef; |
|
||||
} |
|
||||
|
|
||||
public RuleId getRuleId() { |
|
||||
return ruleId; |
|
||||
} |
|
||||
|
|
||||
public boolean isSystemRule() { |
|
||||
return systemRule; |
|
||||
} |
|
||||
|
|
||||
public int getWeight() { |
|
||||
return weight; |
|
||||
} |
|
||||
|
|
||||
public ActorRef getActorRef() { |
|
||||
return actorRef; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public int hashCode() { |
|
||||
final int prime = 31; |
|
||||
int result = 1; |
|
||||
result = prime * result + ((ruleId == null) ? 0 : ruleId.hashCode()); |
|
||||
return result; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public boolean equals(Object obj) { |
|
||||
if (this == obj) |
|
||||
return true; |
|
||||
if (obj == null) |
|
||||
return false; |
|
||||
if (getClass() != obj.getClass()) |
|
||||
return false; |
|
||||
RuleActorMetaData other = (RuleActorMetaData) obj; |
|
||||
if (ruleId == null) { |
|
||||
if (other.ruleId != null) |
|
||||
return false; |
|
||||
} else if (!ruleId.equals(other.ruleId)) |
|
||||
return false; |
|
||||
return true; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public String toString() { |
|
||||
return "RuleActorMetaData [ruleId=" + ruleId + ", systemRule=" + systemRule + ", weight=" + weight + ", actorRef=" + actorRef + "]"; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -1,115 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.rule; |
|
||||
|
|
||||
import com.google.common.util.concurrent.ListenableFuture; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.common.data.Event; |
|
||||
import org.thingsboard.server.common.data.alarm.Alarm; |
|
||||
import org.thingsboard.server.common.data.alarm.AlarmId; |
|
||||
import org.thingsboard.server.common.data.id.*; |
|
||||
import org.thingsboard.server.dao.alarm.AlarmService; |
|
||||
import org.thingsboard.server.dao.event.EventService; |
|
||||
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|
||||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|
||||
import org.thingsboard.server.extensions.api.device.DeviceMetaData; |
|
||||
import org.thingsboard.server.extensions.api.rules.RuleContext; |
|
||||
|
|
||||
import java.util.Optional; |
|
||||
import java.util.concurrent.ExecutionException; |
|
||||
|
|
||||
public class RuleProcessingContext implements RuleContext { |
|
||||
|
|
||||
private final TimeseriesService tsService; |
|
||||
private final EventService eventService; |
|
||||
private final AlarmService alarmService; |
|
||||
private final RuleId ruleId; |
|
||||
private TenantId tenantId; |
|
||||
private CustomerId customerId; |
|
||||
private DeviceId deviceId; |
|
||||
private DeviceMetaData deviceMetaData; |
|
||||
|
|
||||
RuleProcessingContext(ActorSystemContext systemContext, RuleId ruleId) { |
|
||||
this.tsService = systemContext.getTsService(); |
|
||||
this.eventService = systemContext.getEventService(); |
|
||||
this.alarmService = systemContext.getAlarmService(); |
|
||||
this.ruleId = ruleId; |
|
||||
} |
|
||||
|
|
||||
void update(ToDeviceActorMsg toDeviceActorMsg, DeviceMetaData deviceMetaData) { |
|
||||
this.tenantId = toDeviceActorMsg.getTenantId(); |
|
||||
this.customerId = toDeviceActorMsg.getCustomerId(); |
|
||||
this.deviceId = toDeviceActorMsg.getDeviceId(); |
|
||||
this.deviceMetaData = deviceMetaData; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public RuleId getRuleId() { |
|
||||
return ruleId; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public DeviceMetaData getDeviceMetaData() { |
|
||||
return deviceMetaData; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Event save(Event event) { |
|
||||
checkEvent(event); |
|
||||
return eventService.save(event); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Optional<Event> saveIfNotExists(Event event) { |
|
||||
checkEvent(event); |
|
||||
return eventService.saveIfNotExists(event); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Optional<Event> findEvent(String eventType, String eventUid) { |
|
||||
return eventService.findEvent(tenantId, deviceId, eventType, eventUid); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public Alarm createOrUpdateAlarm(Alarm alarm) { |
|
||||
alarm.setTenantId(tenantId); |
|
||||
return alarmService.createOrUpdateAlarm(alarm); |
|
||||
} |
|
||||
|
|
||||
public Optional<Alarm> findLatestAlarm(EntityId originator, String alarmType) { |
|
||||
try { |
|
||||
return Optional.ofNullable(alarmService.findLatestByOriginatorAndType(tenantId, originator, alarmType).get()); |
|
||||
} catch (InterruptedException | ExecutionException e) { |
|
||||
throw new RuntimeException("Failed to lookup alarm!", e); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public ListenableFuture<Boolean> clearAlarm(AlarmId alarmId, long clearTs) { |
|
||||
return alarmService.clearAlarm(alarmId, clearTs); |
|
||||
} |
|
||||
|
|
||||
private void checkEvent(Event event) { |
|
||||
if (event.getTenantId() == null) { |
|
||||
event.setTenantId(tenantId); |
|
||||
} else if (!tenantId.equals(event.getTenantId())) { |
|
||||
throw new IllegalArgumentException("Invalid Tenant id!"); |
|
||||
} |
|
||||
if (event.getEntityId() == null) { |
|
||||
event.setEntityId(deviceId); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,241 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import akka.actor.ActorRef; |
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import org.thingsboard.rule.engine.api.ListeningExecutor; |
||||
|
import org.thingsboard.rule.engine.api.MailService; |
||||
|
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; |
||||
|
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; |
||||
|
import org.thingsboard.rule.engine.api.RuleEngineRpcService; |
||||
|
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; |
||||
|
import org.thingsboard.rule.engine.api.ScriptEngine; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbRelationTypes; |
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.RuleNodeId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; |
||||
|
import org.thingsboard.server.common.data.rule.RuleNode; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsgMetaData; |
||||
|
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; |
||||
|
import org.thingsboard.server.dao.alarm.AlarmService; |
||||
|
import org.thingsboard.server.dao.asset.AssetService; |
||||
|
import org.thingsboard.server.dao.attributes.AttributesService; |
||||
|
import org.thingsboard.server.dao.customer.CustomerService; |
||||
|
import org.thingsboard.server.dao.device.DeviceService; |
||||
|
import org.thingsboard.server.dao.relation.RelationService; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
||||
|
import org.thingsboard.server.dao.user.UserService; |
||||
|
import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; |
||||
|
import scala.concurrent.duration.Duration; |
||||
|
|
||||
|
import java.util.Collections; |
||||
|
import java.util.Set; |
||||
|
import java.util.concurrent.TimeUnit; |
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.03.18. |
||||
|
*/ |
||||
|
class DefaultTbContext implements TbContext { |
||||
|
|
||||
|
private final ActorSystemContext mainCtx; |
||||
|
private final RuleNodeCtx nodeCtx; |
||||
|
|
||||
|
public DefaultTbContext(ActorSystemContext mainCtx, RuleNodeCtx nodeCtx) { |
||||
|
this.mainCtx = mainCtx; |
||||
|
this.nodeCtx = nodeCtx; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void tellNext(TbMsg msg, String relationType) { |
||||
|
tellNext(msg, Collections.singleton(relationType), null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void tellNext(TbMsg msg, Set<String> relationTypes) { |
||||
|
tellNext(msg, relationTypes, null); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void tellNext(TbMsg msg, String relationType, Throwable th) { |
||||
|
tellNext(msg, Collections.singleton(relationType), th); |
||||
|
} |
||||
|
|
||||
|
private void tellNext(TbMsg msg, Set<String> relationTypes, Throwable th) { |
||||
|
if (nodeCtx.getSelf().isDebugMode()) { |
||||
|
relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th)); |
||||
|
} |
||||
|
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg), nodeCtx.getSelfActor()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void tellSelf(TbMsg msg, long delayMs) { |
||||
|
//TODO: add persistence layer
|
||||
|
scheduleMsgWithDelay(new RuleNodeToSelfMsg(msg), delayMs, nodeCtx.getSelfActor()); |
||||
|
} |
||||
|
|
||||
|
private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) { |
||||
|
mainCtx.getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, mainCtx.getActorSystem().dispatcher(), nodeCtx.getSelfActor()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void tellFailure(TbMsg msg, Throwable th) { |
||||
|
if (nodeCtx.getSelf().isDebugMode()) { |
||||
|
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th); |
||||
|
} |
||||
|
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE), msg), nodeCtx.getSelfActor()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void updateSelf(RuleNode self) { |
||||
|
nodeCtx.setSelf(self); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { |
||||
|
return new TbMsg(UUIDs.timeBased(), type, originator, metaData.copy(), data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), mainCtx.getQueuePartitionId()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { |
||||
|
return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), mainCtx.getQueuePartitionId()); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RuleNodeId getSelfId() { |
||||
|
return nodeCtx.getSelf().getId(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TenantId getTenantId() { |
||||
|
return nodeCtx.getTenantId(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListeningExecutor getJsExecutor() { |
||||
|
return mainCtx.getJsExecutor(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListeningExecutor getMailExecutor() { |
||||
|
return mainCtx.getMailExecutor(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListeningExecutor getDbCallbackExecutor() { |
||||
|
return mainCtx.getDbCallbackExecutor(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ListeningExecutor getExternalCallExecutor() { |
||||
|
return mainCtx.getExternalCallExecutorService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public ScriptEngine createJsScriptEngine(String script, String... argNames) { |
||||
|
return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), script, argNames); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public AttributesService getAttributesService() { |
||||
|
return mainCtx.getAttributesService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public CustomerService getCustomerService() { |
||||
|
return mainCtx.getCustomerService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public UserService getUserService() { |
||||
|
return mainCtx.getUserService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public AssetService getAssetService() { |
||||
|
return mainCtx.getAssetService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public DeviceService getDeviceService() { |
||||
|
return mainCtx.getDeviceService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public AlarmService getAlarmService() { |
||||
|
return mainCtx.getAlarmService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RuleChainService getRuleChainService() { |
||||
|
return mainCtx.getRuleChainService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public TimeseriesService getTimeseriesService() { |
||||
|
return mainCtx.getTsService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RuleEngineTelemetryService getTelemetryService() { |
||||
|
return mainCtx.getTsSubService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RelationService getRelationService() { |
||||
|
return mainCtx.getRelationService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public MailService getMailService() { |
||||
|
if (mainCtx.isAllowSystemMailService()) { |
||||
|
return mainCtx.getMailService(); |
||||
|
} else { |
||||
|
throw new RuntimeException("Access to System Mail Service is forbidden!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RuleEngineRpcService getRpcService() { |
||||
|
return new RuleEngineRpcService() { |
||||
|
@Override |
||||
|
public void sendRpcReply(DeviceId deviceId, int requestId, String body) { |
||||
|
mainCtx.getDeviceRpcService().sendRpcReplyToDevice(nodeCtx.getTenantId(), deviceId, requestId, body); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void sendRpcRequest(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) { |
||||
|
ToDeviceRpcRequest request = new ToDeviceRpcRequest(UUIDs.timeBased(), nodeCtx.getTenantId(), src.getDeviceId(), |
||||
|
src.isOneway(), System.currentTimeMillis() + src.getTimeout(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody())); |
||||
|
mainCtx.getDeviceRpcService().processRpcRequestToDevice(request, response -> { |
||||
|
consumer.accept(RuleEngineDeviceRpcResponse.builder() |
||||
|
.deviceId(src.getDeviceId()) |
||||
|
.requestId(src.getRequestId()) |
||||
|
.error(response.getError()) |
||||
|
.response(response.getResponse()) |
||||
|
.build()); |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,97 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import akka.actor.OneForOneStrategy; |
||||
|
import akka.actor.SupervisorStrategy; |
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; |
||||
|
import org.thingsboard.server.actors.service.ComponentActor; |
||||
|
import org.thingsboard.server.actors.service.ContextBasedCreator; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
||||
|
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; |
||||
|
import scala.concurrent.duration.Duration; |
||||
|
|
||||
|
public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMessageProcessor> { |
||||
|
|
||||
|
private RuleChainActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId) { |
||||
|
super(systemContext, tenantId, ruleChainId); |
||||
|
setProcessor(new RuleChainActorMessageProcessor(tenantId, ruleChainId, systemContext, |
||||
|
logger, context().parent(), context().self())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected boolean process(TbActorMsg msg) { |
||||
|
switch (msg.getMsgType()) { |
||||
|
case COMPONENT_LIFE_CYCLE_MSG: |
||||
|
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
||||
|
break; |
||||
|
case SERVICE_TO_RULE_ENGINE_MSG: |
||||
|
processor.onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); |
||||
|
break; |
||||
|
case DEVICE_ACTOR_TO_RULE_ENGINE_MSG: |
||||
|
processor.onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg); |
||||
|
break; |
||||
|
case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG: |
||||
|
processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg); |
||||
|
break; |
||||
|
case RULE_CHAIN_TO_RULE_CHAIN_MSG: |
||||
|
processor.onRuleChainToRuleChainMsg((RuleChainToRuleChainMsg) msg); |
||||
|
break; |
||||
|
case CLUSTER_EVENT_MSG: |
||||
|
break; |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
public static class ActorCreator extends ContextBasedCreator<RuleChainActor> { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
private final TenantId tenantId; |
||||
|
private final RuleChainId ruleChainId; |
||||
|
|
||||
|
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId pluginId) { |
||||
|
super(context); |
||||
|
this.tenantId = tenantId; |
||||
|
this.ruleChainId = pluginId; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RuleChainActor create() throws Exception { |
||||
|
return new RuleChainActor(context, tenantId, ruleChainId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected long getErrorPersistFrequency() { |
||||
|
return systemContext.getRuleChainErrorPersistFrequency(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public SupervisorStrategy supervisorStrategy() { |
||||
|
return strategy; |
||||
|
} |
||||
|
|
||||
|
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> { |
||||
|
logAndPersist("Unknown Failure", ActorSystemContext.toException(t)); |
||||
|
return SupervisorStrategy.resume(); |
||||
|
}); |
||||
|
} |
||||
@ -0,0 +1,297 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import akka.actor.ActorContext; |
||||
|
import akka.actor.ActorRef; |
||||
|
import akka.actor.Props; |
||||
|
import akka.event.LoggingAdapter; |
||||
|
import com.datastax.driver.core.utils.UUIDs; |
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; |
||||
|
import org.thingsboard.server.actors.device.RuleEngineQueuePutAckMsg; |
||||
|
import org.thingsboard.server.actors.service.DefaultActorService; |
||||
|
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.id.RuleNodeId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
||||
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
||||
|
import org.thingsboard.server.common.data.rule.RuleChain; |
||||
|
import org.thingsboard.server.common.data.rule.RuleNode; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
||||
|
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
||||
|
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.Collections; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.Set; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
/** |
||||
|
* @author Andrew Shvayka |
||||
|
*/ |
||||
|
public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleChainId> { |
||||
|
|
||||
|
private static final long DEFAULT_CLUSTER_PARTITION = 0L; |
||||
|
private final ActorRef parent; |
||||
|
private final ActorRef self; |
||||
|
private final Map<RuleNodeId, RuleNodeCtx> nodeActors; |
||||
|
private final Map<RuleNodeId, List<RuleNodeRelation>> nodeRoutes; |
||||
|
private final RuleChainService service; |
||||
|
|
||||
|
private RuleNodeId firstId; |
||||
|
private RuleNodeCtx firstNode; |
||||
|
private boolean started; |
||||
|
|
||||
|
RuleChainActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, ActorSystemContext systemContext |
||||
|
, LoggingAdapter logger, ActorRef parent, ActorRef self) { |
||||
|
super(systemContext, logger, tenantId, ruleChainId); |
||||
|
this.parent = parent; |
||||
|
this.self = self; |
||||
|
this.nodeActors = new HashMap<>(); |
||||
|
this.nodeRoutes = new HashMap<>(); |
||||
|
this.service = systemContext.getRuleChainService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void start(ActorContext context) throws Exception { |
||||
|
if (!started) { |
||||
|
RuleChain ruleChain = service.findRuleChainById(entityId); |
||||
|
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId); |
||||
|
// Creating and starting the actors;
|
||||
|
for (RuleNode ruleNode : ruleNodeList) { |
||||
|
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); |
||||
|
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); |
||||
|
} |
||||
|
initRoutes(ruleChain, ruleNodeList); |
||||
|
reprocess(ruleNodeList); |
||||
|
started = true; |
||||
|
} else { |
||||
|
onUpdate(context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void reprocess(List<RuleNode> ruleNodeList) { |
||||
|
for (RuleNode ruleNode : ruleNodeList) { |
||||
|
for (TbMsg tbMsg : queue.findUnprocessed(tenantId, ruleNode.getId().getId(), systemContext.getQueuePartitionId())) { |
||||
|
pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg, ""); |
||||
|
} |
||||
|
} |
||||
|
if (firstNode != null) { |
||||
|
for (TbMsg tbMsg : queue.findUnprocessed(tenantId, entityId.getId(), systemContext.getQueuePartitionId())) { |
||||
|
pushMsgToNode(firstNode, tbMsg, ""); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onUpdate(ActorContext context) throws Exception { |
||||
|
RuleChain ruleChain = service.findRuleChainById(entityId); |
||||
|
List<RuleNode> ruleNodeList = service.getRuleChainNodes(entityId); |
||||
|
|
||||
|
for (RuleNode ruleNode : ruleNodeList) { |
||||
|
RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); |
||||
|
if (existing == null) { |
||||
|
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); |
||||
|
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); |
||||
|
} else { |
||||
|
existing.setSelf(ruleNode); |
||||
|
existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); |
||||
|
List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); |
||||
|
removedRules.forEach(ruleNodeId -> { |
||||
|
RuleNodeCtx removed = nodeActors.remove(ruleNodeId); |
||||
|
removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); |
||||
|
}); |
||||
|
|
||||
|
initRoutes(ruleChain, ruleNodeList); |
||||
|
reprocess(ruleNodeList); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void stop(ActorContext context) throws Exception { |
||||
|
nodeActors.values().stream().map(RuleNodeCtx::getSelfActor).forEach(context::stop); |
||||
|
nodeActors.clear(); |
||||
|
nodeRoutes.clear(); |
||||
|
context.stop(self); |
||||
|
started = false; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
private ActorRef createRuleNodeActor(ActorContext context, RuleNode ruleNode) { |
||||
|
String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ? |
||||
|
DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME; |
||||
|
return context.actorOf( |
||||
|
Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getId())) |
||||
|
.withDispatcher(dispatcherName), ruleNode.getId().toString()); |
||||
|
} |
||||
|
|
||||
|
private void initRoutes(RuleChain ruleChain, List<RuleNode> ruleNodeList) { |
||||
|
nodeRoutes.clear(); |
||||
|
// Populating the routes map;
|
||||
|
for (RuleNode ruleNode : ruleNodeList) { |
||||
|
List<EntityRelation> relations = service.getRuleNodeRelations(ruleNode.getId()); |
||||
|
if (relations.size() == 0) { |
||||
|
nodeRoutes.put(ruleNode.getId(), Collections.emptyList()); |
||||
|
} else { |
||||
|
for (EntityRelation relation : relations) { |
||||
|
if (relation.getTo().getEntityType() == EntityType.RULE_NODE) { |
||||
|
RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId())); |
||||
|
if (ruleNodeCtx == null) { |
||||
|
throw new IllegalArgumentException("Rule Node [" + relation.getFrom() + "] has invalid relation to Rule node [" + relation.getTo() + "]"); |
||||
|
} |
||||
|
} |
||||
|
nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>()) |
||||
|
.add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType())); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
firstId = ruleChain.getFirstRuleNodeId(); |
||||
|
firstNode = nodeActors.get(ruleChain.getFirstRuleNodeId()); |
||||
|
state = ComponentLifecycleState.ACTIVE; |
||||
|
} |
||||
|
|
||||
|
void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg envelope) { |
||||
|
checkActive(); |
||||
|
if (firstNode != null) { |
||||
|
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> pushMsgToNode(firstNode, msg, "")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) { |
||||
|
checkActive(); |
||||
|
if (firstNode != null) { |
||||
|
putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> { |
||||
|
pushMsgToNode(firstNode, msg, ""); |
||||
|
envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) { |
||||
|
checkActive(); |
||||
|
if (envelope.isEnqueue()) { |
||||
|
if (firstNode != null) { |
||||
|
putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg, envelope.getFromRelationType())); |
||||
|
} |
||||
|
} else { |
||||
|
if (firstNode != null) { |
||||
|
pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType()); |
||||
|
} else { |
||||
|
TbMsg msg = envelope.getMsg(); |
||||
|
EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId(); |
||||
|
queue.ack(tenantId, envelope.getMsg(), ackId.getId(), msg.getClusterPartition()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) { |
||||
|
checkActive(); |
||||
|
RuleNodeId originator = envelope.getOriginator(); |
||||
|
List<RuleNodeRelation> relations = nodeRoutes.get(originator).stream() |
||||
|
.filter(r -> contains(envelope.getRelationTypes(), r.getType())) |
||||
|
.collect(Collectors.toList()); |
||||
|
|
||||
|
TbMsg msg = envelope.getMsg(); |
||||
|
int relationsCount = relations.size(); |
||||
|
EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId(); |
||||
|
if (relationsCount == 0) { |
||||
|
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition()); |
||||
|
} else if (relationsCount == 1) { |
||||
|
for (RuleNodeRelation relation : relations) { |
||||
|
pushToTarget(msg, relation.getOut(), relation.getType()); |
||||
|
} |
||||
|
} else { |
||||
|
for (RuleNodeRelation relation : relations) { |
||||
|
EntityId target = relation.getOut(); |
||||
|
switch (target.getEntityType()) { |
||||
|
case RULE_NODE: |
||||
|
enqueueAndForwardMsgCopyToNode(msg, target, relation.getType()); |
||||
|
break; |
||||
|
case RULE_CHAIN: |
||||
|
enqueueAndForwardMsgCopyToChain(msg, target, relation.getType()); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
//TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
|
||||
|
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private boolean contains(Set<String> relationTypes, String type) { |
||||
|
if (relationTypes == null) { |
||||
|
return true; |
||||
|
} |
||||
|
for (String relationType : relationTypes) { |
||||
|
if (relationType.equalsIgnoreCase(type)) { |
||||
|
return true; |
||||
|
} |
||||
|
} |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target, String fromRelationType) { |
||||
|
RuleChainId targetRCId = new RuleChainId(target.getId()); |
||||
|
TbMsg copyMsg = msg.copy(UUIDs.timeBased(), targetRCId, null, DEFAULT_CLUSTER_PARTITION); |
||||
|
parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, copyMsg, fromRelationType, true), self); |
||||
|
} |
||||
|
|
||||
|
private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target, String fromRelationType) { |
||||
|
RuleNodeId targetId = new RuleNodeId(target.getId()); |
||||
|
RuleNodeCtx targetNodeCtx = nodeActors.get(targetId); |
||||
|
TbMsg copy = msg.copy(UUIDs.timeBased(), entityId, targetId, DEFAULT_CLUSTER_PARTITION); |
||||
|
putToQueue(copy, queuedMsg -> pushMsgToNode(targetNodeCtx, queuedMsg, fromRelationType)); |
||||
|
} |
||||
|
|
||||
|
private void pushToTarget(TbMsg msg, EntityId target, String fromRelationType) { |
||||
|
switch (target.getEntityType()) { |
||||
|
case RULE_NODE: |
||||
|
pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg, fromRelationType); |
||||
|
break; |
||||
|
case RULE_CHAIN: |
||||
|
parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, fromRelationType, false), self); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void pushMsgToNode(RuleNodeCtx nodeCtx, TbMsg msg, String fromRelationType) { |
||||
|
if (nodeCtx != null) { |
||||
|
nodeCtx.getSelfActor().tell(new RuleChainToRuleNodeMsg(new DefaultTbContext(systemContext, nodeCtx), msg, fromRelationType), self); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private TbMsg enrichWithRuleChainId(TbMsg tbMsg) { |
||||
|
// We don't put firstNodeId because it may change over time;
|
||||
|
return new TbMsg(tbMsg.getId(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getData(), entityId, null, systemContext.getQueuePartitionId()); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import akka.actor.ActorRef; |
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.service.ContextAwareActor; |
||||
|
import org.thingsboard.server.actors.shared.rulechain.RuleChainManager; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 15.03.18. |
||||
|
*/ |
||||
|
public abstract class RuleChainManagerActor extends ContextAwareActor { |
||||
|
|
||||
|
protected final RuleChainManager ruleChainManager; |
||||
|
protected final RuleChainService ruleChainService; |
||||
|
|
||||
|
public RuleChainManagerActor(ActorSystemContext systemContext, RuleChainManager ruleChainManager) { |
||||
|
super(systemContext); |
||||
|
this.ruleChainManager = ruleChainManager; |
||||
|
this.ruleChainService = systemContext.getRuleChainService(); |
||||
|
} |
||||
|
|
||||
|
protected void initRuleChains() { |
||||
|
ruleChainManager.init(this.context()); |
||||
|
} |
||||
|
|
||||
|
protected ActorRef getEntityActorRef(EntityId entityId) { |
||||
|
ActorRef target = null; |
||||
|
switch (entityId.getEntityType()) { |
||||
|
case RULE_CHAIN: |
||||
|
target = ruleChainManager.getOrCreateActor(this.context(), (RuleChainId) entityId); |
||||
|
break; |
||||
|
} |
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
protected void broadcast(Object msg) { |
||||
|
ruleChainManager.broadcast(msg); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,40 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.msg.MsgType; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.03.18. |
||||
|
*/ |
||||
|
@Data |
||||
|
public final class RuleChainToRuleChainMsg implements TbActorMsg { |
||||
|
|
||||
|
private final RuleChainId target; |
||||
|
private final RuleChainId source; |
||||
|
private final TbMsg msg; |
||||
|
private final String fromRelationType; |
||||
|
private final boolean enqueue; |
||||
|
|
||||
|
@Override |
||||
|
public MsgType getMsgType() { |
||||
|
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.server.common.msg.MsgType; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.03.18. |
||||
|
*/ |
||||
|
@Data |
||||
|
final class RuleChainToRuleNodeMsg implements TbActorMsg { |
||||
|
|
||||
|
private final TbContext ctx; |
||||
|
private final TbMsg msg; |
||||
|
private final String fromRelationType; |
||||
|
|
||||
|
@Override |
||||
|
public MsgType getMsgType() { |
||||
|
return MsgType.RULE_CHAIN_TO_RULE_MSG; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,109 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.service.ComponentActor; |
||||
|
import org.thingsboard.server.actors.service.ContextBasedCreator; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.id.RuleNodeId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; |
||||
|
|
||||
|
public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessageProcessor> { |
||||
|
|
||||
|
private final RuleChainId ruleChainId; |
||||
|
|
||||
|
private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { |
||||
|
super(systemContext, tenantId, ruleNodeId); |
||||
|
this.ruleChainId = ruleChainId; |
||||
|
setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext, |
||||
|
logger, context().parent(), context().self())); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected boolean process(TbActorMsg msg) { |
||||
|
switch (msg.getMsgType()) { |
||||
|
case COMPONENT_LIFE_CYCLE_MSG: |
||||
|
onComponentLifecycleMsg((ComponentLifecycleMsg) msg); |
||||
|
break; |
||||
|
case RULE_CHAIN_TO_RULE_MSG: |
||||
|
onRuleChainToRuleNodeMsg((RuleChainToRuleNodeMsg) msg); |
||||
|
break; |
||||
|
case RULE_TO_SELF_ERROR_MSG: |
||||
|
onRuleNodeToSelfErrorMsg((RuleNodeToSelfErrorMsg) msg); |
||||
|
break; |
||||
|
case RULE_TO_SELF_MSG: |
||||
|
onRuleNodeToSelfMsg((RuleNodeToSelfMsg) msg); |
||||
|
break; |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
private void onRuleNodeToSelfMsg(RuleNodeToSelfMsg msg) { |
||||
|
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg()); |
||||
|
try { |
||||
|
processor.onRuleToSelfMsg(msg); |
||||
|
increaseMessagesProcessedCount(); |
||||
|
} catch (Exception e) { |
||||
|
logAndPersist("onRuleMsg", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) { |
||||
|
logger.debug("[{}] Going to process rule msg: {}", id, msg.getMsg()); |
||||
|
try { |
||||
|
processor.onRuleChainToRuleNodeMsg(msg); |
||||
|
increaseMessagesProcessedCount(); |
||||
|
} catch (Exception e) { |
||||
|
logAndPersist("onRuleMsg", e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void onRuleNodeToSelfErrorMsg(RuleNodeToSelfErrorMsg msg) { |
||||
|
logAndPersist("onRuleMsg", ActorSystemContext.toException(msg.getError())); |
||||
|
} |
||||
|
|
||||
|
public static class ActorCreator extends ContextBasedCreator<RuleNodeActor> { |
||||
|
private static final long serialVersionUID = 1L; |
||||
|
|
||||
|
private final TenantId tenantId; |
||||
|
private final RuleChainId ruleChainId; |
||||
|
private final RuleNodeId ruleNodeId; |
||||
|
|
||||
|
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { |
||||
|
super(context); |
||||
|
this.tenantId = tenantId; |
||||
|
this.ruleChainId = ruleChainId; |
||||
|
this.ruleNodeId = ruleNodeId; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public RuleNodeActor create() throws Exception { |
||||
|
return new RuleNodeActor(context, tenantId, ruleChainId, ruleNodeId); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
protected long getErrorPersistFrequency() { |
||||
|
return systemContext.getRuleNodeErrorPersistFrequency(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,121 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import akka.actor.ActorContext; |
||||
|
import akka.actor.ActorRef; |
||||
|
import akka.event.LoggingAdapter; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbNode; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.id.RuleNodeId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
||||
|
import org.thingsboard.server.common.data.rule.RuleNode; |
||||
|
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
|
||||
|
/** |
||||
|
* @author Andrew Shvayka |
||||
|
*/ |
||||
|
public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> { |
||||
|
|
||||
|
private final ActorRef parent; |
||||
|
private final ActorRef self; |
||||
|
private final RuleChainService service; |
||||
|
private RuleNode ruleNode; |
||||
|
private TbNode tbNode; |
||||
|
private TbContext defaultCtx; |
||||
|
|
||||
|
RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext |
||||
|
, LoggingAdapter logger, ActorRef parent, ActorRef self) { |
||||
|
super(systemContext, logger, tenantId, ruleNodeId); |
||||
|
this.parent = parent; |
||||
|
this.self = self; |
||||
|
this.service = systemContext.getRuleChainService(); |
||||
|
this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(entityId); |
||||
|
this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode)); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void start(ActorContext context) throws Exception { |
||||
|
tbNode = initComponent(ruleNode); |
||||
|
state = ComponentLifecycleState.ACTIVE; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onUpdate(ActorContext context) throws Exception { |
||||
|
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(entityId); |
||||
|
boolean restartRequired = !(ruleNode.getType().equals(newRuleNode.getType()) |
||||
|
&& ruleNode.getConfiguration().equals(newRuleNode.getConfiguration())); |
||||
|
this.ruleNode = newRuleNode; |
||||
|
this.defaultCtx.updateSelf(newRuleNode); |
||||
|
if (restartRequired) { |
||||
|
if (tbNode != null) { |
||||
|
tbNode.destroy(); |
||||
|
} |
||||
|
start(context); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void stop(ActorContext context) throws Exception { |
||||
|
if (tbNode != null) { |
||||
|
tbNode.destroy(); |
||||
|
} |
||||
|
context.stop(self); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void onClusterEventMsg(ClusterEventMsg msg) throws Exception { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception { |
||||
|
checkActive(); |
||||
|
if (ruleNode.isDebugMode()) { |
||||
|
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self"); |
||||
|
} |
||||
|
try { |
||||
|
tbNode.onMsg(defaultCtx, msg.getMsg()); |
||||
|
} catch (Exception e) { |
||||
|
defaultCtx.tellFailure(msg.getMsg(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception { |
||||
|
checkActive(); |
||||
|
if (ruleNode.isDebugMode()) { |
||||
|
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType()); |
||||
|
} |
||||
|
try { |
||||
|
tbNode.onMsg(msg.getCtx(), msg.getMsg()); |
||||
|
} catch (Exception e) { |
||||
|
msg.getCtx().tellFailure(msg.getMsg(), e); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private TbNode initComponent(RuleNode ruleNode) throws Exception { |
||||
|
Class<?> componentClazz = Class.forName(ruleNode.getType()); |
||||
|
TbNode tbNode = (TbNode) (componentClazz.newInstance()); |
||||
|
tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration())); |
||||
|
return tbNode; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.msg.MsgType; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.03.18. |
||||
|
*/ |
||||
|
@Data |
||||
|
final class RuleNodeToSelfErrorMsg implements TbActorMsg { |
||||
|
|
||||
|
private final TbMsg msg; |
||||
|
private final Throwable error; |
||||
|
|
||||
|
@Override |
||||
|
public MsgType getMsgType() { |
||||
|
return MsgType.RULE_TO_SELF_ERROR_MSG; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.ruleChain; |
||||
|
|
||||
|
import lombok.Data; |
||||
|
import org.thingsboard.server.common.msg.MsgType; |
||||
|
import org.thingsboard.server.common.msg.TbActorMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 19.03.18. |
||||
|
*/ |
||||
|
@Data |
||||
|
final class RuleNodeToSelfMsg implements TbActorMsg { |
||||
|
|
||||
|
private final TbMsg msg; |
||||
|
|
||||
|
@Override |
||||
|
public MsgType getMsgType() { |
||||
|
return MsgType.RULE_TO_SELF_MSG; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,24 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.service; |
|
||||
|
|
||||
import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; |
|
||||
|
|
||||
public interface WebSocketMsgProcessor { |
|
||||
|
|
||||
void process(PluginWebsocketMsg<?> msg); |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,84 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.shared; |
||||
|
|
||||
|
import akka.actor.ActorContext; |
||||
|
import akka.actor.ActorRef; |
||||
|
import akka.actor.Props; |
||||
|
import akka.actor.UntypedActor; |
||||
|
import akka.japi.Creator; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.service.ContextAwareActor; |
||||
|
import org.thingsboard.server.common.data.SearchTextBased; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.id.UUIDBased; |
||||
|
import org.thingsboard.server.common.data.page.PageDataIterable; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 15.03.18. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public abstract class EntityActorsManager<T extends EntityId, A extends UntypedActor, M extends SearchTextBased<? extends UUIDBased>> { |
||||
|
|
||||
|
protected final ActorSystemContext systemContext; |
||||
|
protected final Map<T, ActorRef> actors; |
||||
|
|
||||
|
public EntityActorsManager(ActorSystemContext systemContext) { |
||||
|
this.systemContext = systemContext; |
||||
|
this.actors = new HashMap<>(); |
||||
|
} |
||||
|
|
||||
|
protected abstract TenantId getTenantId(); |
||||
|
|
||||
|
protected abstract String getDispatcherName(); |
||||
|
|
||||
|
protected abstract Creator<A> creator(T entityId); |
||||
|
|
||||
|
protected abstract PageDataIterable.FetchFunction<M> getFetchEntitiesFunction(); |
||||
|
|
||||
|
public void init(ActorContext context) { |
||||
|
for (M entity : new PageDataIterable<>(getFetchEntitiesFunction(), ContextAwareActor.ENTITY_PACK_LIMIT)) { |
||||
|
T entityId = (T) entity.getId(); |
||||
|
log.debug("[{}|{}] Creating entity actor", entityId.getEntityType(), entityId.getId()); |
||||
|
//TODO: remove this cast making UUIDBased subclass of EntityId an interface and vice versa.
|
||||
|
ActorRef actorRef = getOrCreateActor(context, entityId); |
||||
|
visit(entity, actorRef); |
||||
|
log.debug("[{}|{}] Entity actor created.", entityId.getEntityType(), entityId.getId()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void visit(M entity, ActorRef actorRef) {} |
||||
|
|
||||
|
public ActorRef getOrCreateActor(ActorContext context, T entityId) { |
||||
|
return actors.computeIfAbsent(entityId, eId -> |
||||
|
context.actorOf(Props.create(creator(eId)) |
||||
|
.withDispatcher(getDispatcherName()), eId.toString())); |
||||
|
} |
||||
|
|
||||
|
public void broadcast(Object msg) { |
||||
|
actors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
||||
|
} |
||||
|
|
||||
|
public void remove(T id) { |
||||
|
actors.remove(id); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,77 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.shared.plugin; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.actor.ActorRef; |
|
||||
import akka.actor.Props; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.plugin.PluginActor; |
|
||||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|
||||
import org.thingsboard.server.common.data.id.PluginId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|
||||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|
||||
import org.thingsboard.server.dao.plugin.PluginService; |
|
||||
|
|
||||
import java.util.HashMap; |
|
||||
import java.util.Map; |
|
||||
|
|
||||
@Slf4j |
|
||||
public abstract class PluginManager { |
|
||||
|
|
||||
protected final ActorSystemContext systemContext; |
|
||||
protected final PluginService pluginService; |
|
||||
protected final Map<PluginId, ActorRef> pluginActors; |
|
||||
|
|
||||
public PluginManager(ActorSystemContext systemContext) { |
|
||||
this.systemContext = systemContext; |
|
||||
this.pluginService = systemContext.getPluginService(); |
|
||||
this.pluginActors = new HashMap<>(); |
|
||||
} |
|
||||
|
|
||||
public void init(ActorContext context) { |
|
||||
PageDataIterable<PluginMetaData> pluginIterator = new PageDataIterable<>(getFetchPluginsFunction(), |
|
||||
ContextAwareActor.ENTITY_PACK_LIMIT); |
|
||||
for (PluginMetaData plugin : pluginIterator) { |
|
||||
log.debug("[{}] Creating plugin actor", plugin.getId()); |
|
||||
getOrCreatePluginActor(context, plugin.getId()); |
|
||||
log.debug("Plugin actor created."); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
abstract FetchFunction<PluginMetaData> getFetchPluginsFunction(); |
|
||||
|
|
||||
abstract TenantId getTenantId(); |
|
||||
|
|
||||
abstract String getDispatcherName(); |
|
||||
|
|
||||
public ActorRef getOrCreatePluginActor(ActorContext context, PluginId pluginId) { |
|
||||
return pluginActors.computeIfAbsent(pluginId, pId -> |
|
||||
context.actorOf(Props.create(new PluginActor.ActorCreator(systemContext, getTenantId(), pId)) |
|
||||
.withDispatcher(getDispatcherName()), pId.toString())); |
|
||||
} |
|
||||
|
|
||||
public void broadcast(Object msg) { |
|
||||
pluginActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); |
|
||||
} |
|
||||
|
|
||||
public void remove(PluginId id) { |
|
||||
pluginActors.remove(id); |
|
||||
} |
|
||||
} |
|
||||
@ -1,45 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.shared.plugin; |
|
||||
|
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.service.DefaultActorService; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|
||||
import org.thingsboard.server.common.data.plugin.PluginMetaData; |
|
||||
import org.thingsboard.server.dao.plugin.BasePluginService; |
|
||||
|
|
||||
public class SystemPluginManager extends PluginManager { |
|
||||
|
|
||||
public SystemPluginManager(ActorSystemContext systemContext) { |
|
||||
super(systemContext); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
FetchFunction<PluginMetaData> getFetchPluginsFunction() { |
|
||||
return pluginService::findSystemPlugins; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
TenantId getTenantId() { |
|
||||
return BasePluginService.SYSTEM_TENANT; |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
protected String getDispatcherName() { |
|
||||
return DefaultActorService.SYSTEM_PLUGIN_DISPATCHER_NAME; |
|
||||
} |
|
||||
} |
|
||||
@ -1,135 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.shared.rule; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import akka.actor.ActorRef; |
|
||||
import akka.actor.Props; |
|
||||
import lombok.extern.slf4j.Slf4j; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.rule.RuleActor; |
|
||||
import org.thingsboard.server.actors.rule.RuleActorChain; |
|
||||
import org.thingsboard.server.actors.rule.RuleActorMetaData; |
|
||||
import org.thingsboard.server.actors.rule.SimpleRuleActorChain; |
|
||||
import org.thingsboard.server.actors.service.ContextAwareActor; |
|
||||
import org.thingsboard.server.common.data.id.RuleId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable; |
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|
||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; |
|
||||
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; |
|
||||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|
||||
import org.thingsboard.server.dao.rule.RuleService; |
|
||||
|
|
||||
import java.util.*; |
|
||||
|
|
||||
@Slf4j |
|
||||
public abstract class RuleManager { |
|
||||
|
|
||||
protected final ActorSystemContext systemContext; |
|
||||
protected final RuleService ruleService; |
|
||||
protected final Map<RuleId, ActorRef> ruleActors; |
|
||||
protected final TenantId tenantId; |
|
||||
|
|
||||
private Map<RuleMetaData, RuleActorMetaData> ruleMap; |
|
||||
private RuleActorChain ruleChain; |
|
||||
|
|
||||
public RuleManager(ActorSystemContext systemContext, TenantId tenantId) { |
|
||||
this.systemContext = systemContext; |
|
||||
this.ruleService = systemContext.getRuleService(); |
|
||||
this.ruleActors = new HashMap<>(); |
|
||||
this.tenantId = tenantId; |
|
||||
} |
|
||||
|
|
||||
public void init(ActorContext context) { |
|
||||
doInit(context); |
|
||||
} |
|
||||
|
|
||||
private void doInit(ActorContext context) { |
|
||||
PageDataIterable<RuleMetaData> ruleIterator = new PageDataIterable<>(getFetchRulesFunction(), |
|
||||
ContextAwareActor.ENTITY_PACK_LIMIT); |
|
||||
ruleMap = new HashMap<>(); |
|
||||
|
|
||||
for (RuleMetaData rule : ruleIterator) { |
|
||||
log.debug("[{}] Creating rule actor {}", rule.getId(), rule); |
|
||||
ActorRef ref = getOrCreateRuleActor(context, rule.getId()); |
|
||||
ruleMap.put(rule, RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref)); |
|
||||
log.debug("[{}] Rule actor created.", rule.getId()); |
|
||||
} |
|
||||
|
|
||||
refreshRuleChain(); |
|
||||
} |
|
||||
|
|
||||
public Optional<ActorRef> update(ActorContext context, RuleId ruleId, ComponentLifecycleEvent event) { |
|
||||
if (ruleMap == null) { |
|
||||
doInit(context); |
|
||||
} |
|
||||
RuleMetaData rule; |
|
||||
if (event != ComponentLifecycleEvent.DELETED) { |
|
||||
rule = systemContext.getRuleService().findRuleById(ruleId); |
|
||||
} else { |
|
||||
rule = ruleMap.keySet().stream() |
|
||||
.filter(r -> r.getId().equals(ruleId)) |
|
||||
.peek(r -> r.setState(ComponentLifecycleState.SUSPENDED)) |
|
||||
.findFirst() |
|
||||
.orElse(null); |
|
||||
if (rule != null) { |
|
||||
ruleMap.remove(rule); |
|
||||
ruleActors.remove(ruleId); |
|
||||
} |
|
||||
} |
|
||||
if (rule != null) { |
|
||||
RuleActorMetaData actorMd = ruleMap.get(rule); |
|
||||
if (actorMd == null) { |
|
||||
ActorRef ref = getOrCreateRuleActor(context, rule.getId()); |
|
||||
actorMd = RuleActorMetaData.systemRule(rule.getId(), rule.getWeight(), ref); |
|
||||
ruleMap.put(rule, actorMd); |
|
||||
} |
|
||||
refreshRuleChain(); |
|
||||
return Optional.of(actorMd.getActorRef()); |
|
||||
} else { |
|
||||
log.warn("[{}] Can't process unknown rule!", ruleId); |
|
||||
return Optional.empty(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
abstract FetchFunction<RuleMetaData> getFetchRulesFunction(); |
|
||||
|
|
||||
abstract String getDispatcherName(); |
|
||||
|
|
||||
public ActorRef getOrCreateRuleActor(ActorContext context, RuleId ruleId) { |
|
||||
return ruleActors.computeIfAbsent(ruleId, rId -> |
|
||||
context.actorOf(Props.create(new RuleActor.ActorCreator(systemContext, tenantId, rId)) |
|
||||
.withDispatcher(getDispatcherName()), rId.toString())); |
|
||||
} |
|
||||
|
|
||||
public RuleActorChain getRuleChain(ActorContext context) { |
|
||||
if (ruleChain == null) { |
|
||||
doInit(context); |
|
||||
} |
|
||||
return ruleChain; |
|
||||
} |
|
||||
|
|
||||
private void refreshRuleChain() { |
|
||||
Set<RuleActorMetaData> activeRuleSet = new HashSet<>(); |
|
||||
for (Map.Entry<RuleMetaData, RuleActorMetaData> rule : ruleMap.entrySet()) { |
|
||||
if (rule.getKey().getState() == ComponentLifecycleState.ACTIVE) { |
|
||||
activeRuleSet.add(rule.getValue()); |
|
||||
} |
|
||||
} |
|
||||
ruleChain = new SimpleRuleActorChain(activeRuleSet); |
|
||||
} |
|
||||
} |
|
||||
@ -1,48 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.shared.rule; |
|
||||
|
|
||||
import akka.actor.ActorContext; |
|
||||
import org.thingsboard.server.actors.ActorSystemContext; |
|
||||
import org.thingsboard.server.actors.service.DefaultActorService; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.page.PageDataIterable.FetchFunction; |
|
||||
import org.thingsboard.server.common.data.rule.RuleMetaData; |
|
||||
|
|
||||
public class TenantRuleManager extends RuleManager { |
|
||||
|
|
||||
public TenantRuleManager(ActorSystemContext systemContext, TenantId tenantId) { |
|
||||
super(systemContext, tenantId); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
public void init(ActorContext context) { |
|
||||
if (systemContext.isTenantComponentsInitEnabled()) { |
|
||||
super.init(context); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
FetchFunction<RuleMetaData> getFetchRulesFunction() { |
|
||||
return link -> ruleService.findTenantRules(tenantId, link); |
|
||||
} |
|
||||
|
|
||||
@Override |
|
||||
String getDispatcherName() { |
|
||||
return DefaultActorService.TENANT_RULE_DISPATCHER_NAME; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,59 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.actors.shared.rulechain; |
||||
|
|
||||
|
import akka.actor.ActorRef; |
||||
|
import akka.japi.Creator; |
||||
|
import lombok.Getter; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.thingsboard.server.actors.ActorSystemContext; |
||||
|
import org.thingsboard.server.actors.ruleChain.RuleChainActor; |
||||
|
import org.thingsboard.server.actors.shared.EntityActorsManager; |
||||
|
import org.thingsboard.server.common.data.id.RuleChainId; |
||||
|
import org.thingsboard.server.common.data.rule.RuleChain; |
||||
|
import org.thingsboard.server.dao.rule.RuleChainService; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 15.03.18. |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
public abstract class RuleChainManager extends EntityActorsManager<RuleChainId, RuleChainActor, RuleChain> { |
||||
|
|
||||
|
protected final RuleChainService service; |
||||
|
@Getter |
||||
|
protected RuleChain rootChain; |
||||
|
@Getter |
||||
|
protected ActorRef rootChainActor; |
||||
|
|
||||
|
public RuleChainManager(ActorSystemContext systemContext) { |
||||
|
super(systemContext); |
||||
|
this.service = systemContext.getRuleChainService(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public Creator<RuleChainActor> creator(RuleChainId entityId) { |
||||
|
return new RuleChainActor.ActorCreator(systemContext, getTenantId(), entityId); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void visit(RuleChain entity, ActorRef actorRef) { |
||||
|
if (entity.isRoot()) { |
||||
|
rootChain = entity; |
||||
|
rootChainActor = actorRef; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -1,40 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2018 The Thingsboard Authors |
|
||||
* |
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
||||
* you may not use this file except in compliance with the License. |
|
||||
* You may obtain a copy of the License at |
|
||||
* |
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||
* |
|
||||
* Unless required by applicable law or agreed to in writing, software |
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
||||
* See the License for the specific language governing permissions and |
|
||||
* limitations under the License. |
|
||||
*/ |
|
||||
package org.thingsboard.server.actors.tenant; |
|
||||
|
|
||||
import org.thingsboard.server.actors.rule.RuleActorChain; |
|
||||
import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; |
|
||||
|
|
||||
public class RuleChainDeviceMsg { |
|
||||
|
|
||||
private final ToDeviceActorMsg toDeviceActorMsg; |
|
||||
private final RuleActorChain ruleChain; |
|
||||
|
|
||||
public RuleChainDeviceMsg(ToDeviceActorMsg toDeviceActorMsg, RuleActorChain ruleChain) { |
|
||||
super(); |
|
||||
this.toDeviceActorMsg = toDeviceActorMsg; |
|
||||
this.ruleChain = ruleChain; |
|
||||
} |
|
||||
|
|
||||
public ToDeviceActorMsg getToDeviceActorMsg() { |
|
||||
return toDeviceActorMsg; |
|
||||
} |
|
||||
|
|
||||
public RuleActorChain getRuleChain() { |
|
||||
return ruleChain; |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,32 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2018 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.controller; |
||||
|
|
||||
|
import com.google.common.util.concurrent.FutureCallback; |
||||
|
import org.springframework.http.ResponseEntity; |
||||
|
import org.springframework.web.context.request.async.DeferredResult; |
||||
|
import org.thingsboard.server.service.security.ValidationCallback; |
||||
|
|
||||
|
/** |
||||
|
* Created by ashvayka on 21.02.17. |
||||
|
*/ |
||||
|
public class HttpValidationCallback extends ValidationCallback<DeferredResult<ResponseEntity>> { |
||||
|
|
||||
|
public HttpValidationCallback(DeferredResult<ResponseEntity> response, FutureCallback<DeferredResult<ResponseEntity>> action) { |
||||
|
super(response, action); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue