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