diff --git a/application/src/main/conf/thingsboard.conf b/application/src/main/conf/thingsboard.conf index a6e404d5ad..2baee7de41 100644 --- a/application/src/main/conf/thingsboard.conf +++ b/application/src/main/conf/thingsboard.conf @@ -14,7 +14,7 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@" +export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@" export LOG_FILENAME=${pkg.name}.out export LOADER_PATH=${pkg.installFolder}/conf,${pkg.installFolder}/extensions export SQL_DATA_FOLDER=${pkg.installFolder}/data/sql diff --git a/application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json b/application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json deleted file mode 100644 index 6181f4920c..0000000000 --- a/application/src/main/data/json/demo/plugins/demo_device_messaging_rpc_plugin.json +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/plugins/demo_email_plugin.json b/application/src/main/data/json/demo/plugins/demo_email_plugin.json deleted file mode 100644 index 40225f8c8c..0000000000 --- a/application/src/main/data/json/demo/plugins/demo_email_plugin.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json b/application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json deleted file mode 100644 index d5ac2d4469..0000000000 --- a/application/src/main/data/json/demo/plugins/demo_time_rpc_plugin.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/rules/demo_alarm_rule.json b/application/src/main/data/json/demo/rules/demo_alarm_rule.json deleted file mode 100644 index 12565d8c16..0000000000 --- a/application/src/main/data/json/demo/rules/demo_alarm_rule.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json b/application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json deleted file mode 100644 index 391c121ca2..0000000000 --- a/application/src/main/data/json/demo/rules/demo_gettime_rpc_rule.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json b/application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json deleted file mode 100644 index 50021e4bde..0000000000 --- a/application/src/main/data/json/demo/rules/demo_messaging_rpc_rule.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/system/plugins/system_rpc_plugin.json b/application/src/main/data/json/system/plugins/system_rpc_plugin.json deleted file mode 100644 index 7245faaa5e..0000000000 --- a/application/src/main/data/json/system/plugins/system_rpc_plugin.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/system/plugins/system_telemetry_plugin.json b/application/src/main/data/json/system/plugins/system_telemetry_plugin.json deleted file mode 100644 index 93a40e0ab3..0000000000 --- a/application/src/main/data/json/system/plugins/system_telemetry_plugin.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/system/rules/system_telemetry_rule.json b/application/src/main/data/json/system/rules/system_telemetry_rule.json deleted file mode 100644 index c8166bf710..0000000000 --- a/application/src/main/data/json/system/rules/system_telemetry_rule.json +++ /dev/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 -} \ No newline at end of file diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json new file mode 100644 index 0000000000..7d6da8d1e8 --- /dev/null +++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json @@ -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": true, + "configuration": { + "defaultTTL": 0 + } + }, + { + "additionalInfo": { + "layoutX": 825, + "layoutY": 52 + }, + "type": "org.thingsboard.rule.engine.telemetry.TbMsgAttributesNode", + "name": "save client attributes", + "debugMode": true, + "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 + } +} \ No newline at end of file diff --git a/application/src/main/data/upgrade/1.5.0/schema_update.cql b/application/src/main/data/upgrade/1.5.0/schema_update.cql index 5cdaede0ff..9481fc0501 100644 --- a/application/src/main/data/upgrade/1.5.0/schema_update.cql +++ b/application/src/main/data/upgrade/1.5.0/schema_update.cql @@ -84,6 +84,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_sear CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( id uuid, + rule_chain_id uuid, type text, name text, debug_mode boolean, diff --git a/application/src/main/data/upgrade/1.5.0/schema_update.sql b/application/src/main/data/upgrade/1.5.0/schema_update.sql index ab91166fac..ef5f6db682 100644 --- a/application/src/main/data/upgrade/1.5.0/schema_update.sql +++ b/application/src/main/data/upgrade/1.5.0/schema_update.sql @@ -28,6 +28,7 @@ CREATE TABLE IF NOT EXISTS rule_chain ( 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), diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 50d2530b53..54737f84d5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -49,6 +49,7 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.plugin.PluginService; +import org.thingsboard.server.dao.queue.MsgQueue; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleService; @@ -60,8 +61,11 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.mail.MailExecutorService; +import org.thingsboard.server.service.rpc.DeviceRpcService; import org.thingsboard.server.service.script.JsExecutorService; +import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.io.IOException; @@ -161,6 +165,10 @@ public class ActorSystemContext { @Getter private TelemetrySubscriptionService tsSubService; + @Autowired + @Getter + private DeviceRpcService deviceRpcService; + @Autowired @Getter @Setter @@ -178,25 +186,37 @@ public class ActorSystemContext { @Getter private DbCallbackExecutorService dbCallbackExecutor; + @Autowired + @Getter + private ExternalCallExecutorService externalCallExecutorService; + @Autowired @Getter private MailService mailService; + @Autowired + @Getter + private MsgQueue msgQueue; + + @Autowired + @Getter + private DeviceStateService deviceStateService; + @Value("${actors.session.sync.timeout}") @Getter private long syncSessionTimeout; - @Value("${actors.plugin.termination.delay}") + @Value("${actors.queue.enabled}") @Getter - private long pluginActorTerminationDelay; + private boolean queuePersistenceEnabled; - @Value("${actors.plugin.processing.timeout}") + @Value("${actors.queue.timeout}") @Getter - private long pluginProcessingTimeout; + private long queuePersistenceTimeout; - @Value("${actors.plugin.error_persist_frequency}") + @Value("${actors.client_side_rpc.timeout}") @Getter - private long pluginErrorPersistFrequency; + private long clientSideRpcTimeout; @Value("${actors.rule.chain.error_persist_frequency}") @Getter @@ -206,14 +226,6 @@ public class ActorSystemContext { @Getter private long ruleNodeErrorPersistFrequency; - @Value("${actors.rule.termination.delay}") - @Getter - private long ruleActorTerminationDelay; - - @Value("${actors.rule.error_persist_frequency}") - @Getter - private long ruleErrorPersistFrequency; - @Value("${actors.statistics.enabled}") @Getter private boolean statisticsEnabled; diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index a75158f2dd..4d9b8b4f27 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -29,13 +29,12 @@ import org.thingsboard.server.actors.shared.plugin.SystemPluginManager; import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager; import org.thingsboard.server.actors.tenant.TenantActor; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.id.PluginId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; +import org.thingsboard.server.common.msg.aware.TenantAwareMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.dao.model.ModelConstants; @@ -90,12 +89,23 @@ public class AppActor extends RuleChainManagerActor { @Override protected boolean process(TbActorMsg msg) { switch (msg.getMsgType()) { + case CLUSTER_EVENT_MSG: + broadcast(msg); + break; case COMPONENT_LIFE_CYCLE_MSG: onComponentLifecycleMsg((ComponentLifecycleMsg) msg); break; case SERVICE_TO_RULE_ENGINE_MSG: onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); break; + case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG: + case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG: + case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG: + case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG: + case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG: + case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: + onToDeviceActorMsg((TenantAwareMsg) msg); + break; default: return false; } @@ -110,48 +120,12 @@ public class AppActor extends RuleChainManagerActor { } } - -// @Override -// public void onReceive(Object msg) throws Exception { -// logger.debug("Received message: {}", msg); -// if (msg instanceof ToDeviceActorMsg) { -// processDeviceMsg((ToDeviceActorMsg) msg); -// } else if (msg instanceof ToPluginActorMsg) { -// onToPluginMsg((ToPluginActorMsg) msg); -// } else if (msg instanceof ToDeviceActorNotificationMsg) { -// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); -// } else if (msg instanceof Terminated) { -// processTermination((Terminated) msg); -// } else if (msg instanceof ClusterEventMsg) { -// broadcast(msg); -// } else if (msg instanceof ComponentLifecycleMsg) { -// onComponentLifecycleMsg((ComponentLifecycleMsg) msg); -// } else if (msg instanceof PluginTerminationMsg) { -// onPluginTerminated((PluginTerminationMsg) msg); -// } else { -// logger.warning("Unknown message: {}!", msg); -// } -// } - - private void onPluginTerminated(PluginTerminationMsg msg) { - pluginManager.remove(msg.getId()); - } - - private void broadcast(Object msg) { - pluginManager.broadcast(msg); + @Override + protected void broadcast(Object msg) { + super.broadcast(msg); tenantActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); } - private void onToPluginMsg(ToPluginActorMsg msg) { - ActorRef target; - if (SYSTEM_TENANT.equals(msg.getPluginTenantId())) { - target = pluginManager.getOrCreateActor(this.context(), msg.getPluginId()); - } else { - target = getOrCreateTenantActor(msg.getPluginTenantId()); - } - target.tell(msg, ActorRef.noSender()); - } - private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { ActorRef target; if (SYSTEM_TENANT.equals(msg.getTenantId())) { @@ -166,17 +140,17 @@ public class AppActor extends RuleChainManagerActor { } } - private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) { + private void onToDeviceActorMsg(TenantAwareMsg msg) { getOrCreateTenantActor(msg.getTenantId()).tell(msg, ActorRef.noSender()); } - private void processDeviceMsg(ToDeviceActorMsg toDeviceActorMsg) { - TenantId tenantId = toDeviceActorMsg.getTenantId(); + private void processDeviceMsg(DeviceToDeviceActorMsg deviceToDeviceActorMsg) { + TenantId tenantId = deviceToDeviceActorMsg.getTenantId(); ActorRef tenantActor = getOrCreateTenantActor(tenantId); - if (toDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) { -// tenantActor.tell(new RuleChainDeviceMsg(toDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self()); + if (deviceToDeviceActorMsg.getPayload().getMsgType().requiresRulesProcessing()) { +// tenantActor.tell(new RuleChainDeviceMsg(deviceToDeviceActorMsg, ruleManager.getRuleChain(this.context())), context().self()); } else { - tenantActor.tell(toDeviceActorMsg, context().self()); + tenantActor.tell(deviceToDeviceActorMsg, context().self()); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java index 87bc9926d4..93df1808d5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java @@ -24,59 +24,66 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; +import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg; +import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg; +import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; -import org.thingsboard.server.extensions.api.device.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg; -import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; +import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; public class DeviceActor extends ContextAwareActor { private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); - private final TenantId tenantId; - private final DeviceId deviceId; private final DeviceActorMessageProcessor processor; private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { super(systemContext); - this.tenantId = tenantId; - this.deviceId = deviceId; - this.processor = new DeviceActorMessageProcessor(systemContext, logger, deviceId); + this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId); } @Override protected boolean process(TbActorMsg msg) { - return false; - } - - @Override - public void onReceive(Object msg) throws Exception { -// if (msg instanceof RuleChainDeviceMsg) { -// processor.process(context(), (RuleChainDeviceMsg) msg); -// } else if (msg instanceof RulesProcessedMsg) { -// processor.onRulesProcessedMsg(context(), (RulesProcessedMsg) msg); - if (msg instanceof ToDeviceActorMsg) { - processor.process(context(), (ToDeviceActorMsg) msg); - } else if (msg instanceof ToDeviceActorNotificationMsg) { - if (msg instanceof DeviceAttributesEventNotificationMsg) { + switch (msg.getMsgType()) { + case CLUSTER_EVENT_MSG: + processor.processClusterEventMsg((ClusterEventMsg) msg); + break; + case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG: + processor.process(context(), (DeviceToDeviceActorMsg) msg); + break; + case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG: processor.processAttributesUpdate(context(), (DeviceAttributesEventNotificationMsg) msg); - } else if (msg instanceof ToDeviceRpcRequestPluginMsg) { - processor.processRpcRequest(context(), (ToDeviceRpcRequestPluginMsg) msg); - } else if (msg instanceof DeviceCredentialsUpdateNotificationMsg){ + break; + case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG: processor.processCredentialsUpdate(); - } else if (msg instanceof DeviceNameOrTypeUpdateMsg){ + break; + case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG: processor.processNameOrTypeUpdate((DeviceNameOrTypeUpdateMsg) msg); - } - } else if (msg instanceof TimeoutMsg) { - processor.processTimeout(context(), (TimeoutMsg) msg); - } else if (msg instanceof ClusterEventMsg) { - processor.processClusterEventMsg((ClusterEventMsg) msg); - } else { - logger.debug("[{}][{}] Unknown msg type.", tenantId, deviceId, msg.getClass().getName()); + break; + case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG: + processor.processRpcRequest(context(), (ToDeviceRpcRequestActorMsg) msg); + break; + case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: + processor.processToServerRPCResponse(context(), (ToServerRpcResponseActorMsg) msg); + break; + case DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG: + processor.processServerSideRpcTimeout(context(), (DeviceActorServerSideRpcTimeoutMsg) msg); + break; + case DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG: + processor.processClientSideRpcTimeout(context(), (DeviceActorClientSideRpcTimeoutMsg) msg); + break; + case DEVICE_ACTOR_QUEUE_TIMEOUT_MSG: + processor.processQueueTimeout(context(), (DeviceActorQueueTimeoutMsg) msg); + break; + case RULE_ENGINE_QUEUE_PUT_ACK_MSG: + processor.processQueueAck(context(), (RuleEngineQueuePutAckMsg) msg); + break; + default: + return false; } + return true; } public static class ActorCreator extends ContextBasedCreator { diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index 3644a491a4..5112f220de 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -18,30 +18,76 @@ package org.thingsboard.server.actors.device; import akka.actor.ActorContext; import akka.actor.ActorRef; import akka.event.LoggingAdapter; +import com.datastax.driver.core.utils.UUIDs; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.core.*; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.core.AttributesUpdateNotification; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; +import org.thingsboard.server.common.msg.core.BasicCommandAckResponse; +import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse; +import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; +import org.thingsboard.server.common.msg.core.BasicToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.core.GetAttributesRequest; +import org.thingsboard.server.common.msg.core.RuleEngineError; +import org.thingsboard.server.common.msg.core.RuleEngineErrorMsg; +import org.thingsboard.server.common.msg.core.SessionCloseMsg; +import org.thingsboard.server.common.msg.core.SessionCloseNotification; +import org.thingsboard.server.common.msg.core.SessionOpenMsg; +import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; +import org.thingsboard.server.common.msg.core.ToDeviceRpcRequestMsg; +import org.thingsboard.server.common.msg.core.ToDeviceRpcResponseMsg; +import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; +import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; -import org.thingsboard.server.extensions.api.device.DeviceAttributes; +import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg; +import org.thingsboard.server.common.msg.timeout.DeviceActorQueueTimeoutMsg; +import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.extensions.api.device.DeviceNameOrTypeUpdateMsg; -import org.thingsboard.server.extensions.api.plugins.msg.*; - -import java.util.*; -import java.util.concurrent.ExecutionException; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.RpcError; +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; +import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Predicate; @@ -52,46 +98,46 @@ import java.util.stream.Collectors; */ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { + private final TenantId tenantId; private final DeviceId deviceId; private final Map sessions; private final Map attributeSubscriptions; private final Map rpcSubscriptions; + private final Map toDeviceRpcPendingMap; + private final Map toServerRpcPendingMap; + private final Map pendingMsgs; - private final Map rpcPendingMap; + private final Gson gson = new Gson(); + private final JsonParser jsonParser = new JsonParser(); private int rpcSeq = 0; private String deviceName; private String deviceType; - private DeviceAttributes deviceAttributes; + private TbMsgMetaData defaultMetaData; - public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, DeviceId deviceId) { + public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) { super(systemContext, logger); + this.tenantId = tenantId; this.deviceId = deviceId; this.sessions = new HashMap<>(); this.attributeSubscriptions = new HashMap<>(); this.rpcSubscriptions = new HashMap<>(); - this.rpcPendingMap = new HashMap<>(); + this.toDeviceRpcPendingMap = new HashMap<>(); + this.toServerRpcPendingMap = new HashMap<>(); + this.pendingMsgs = new HashMap<>(); initAttributes(); } private void initAttributes() { - //TODO: add invalidation of deviceType cache. Device device = systemContext.getDeviceService().findDeviceById(deviceId); this.deviceName = device.getName(); this.deviceType = device.getType(); - this.deviceAttributes = new DeviceAttributes(fetchAttributes(DataConstants.CLIENT_SCOPE), - fetchAttributes(DataConstants.SERVER_SCOPE), fetchAttributes(DataConstants.SHARED_SCOPE)); - } - - private void refreshAttributes(DeviceAttributesEventNotificationMsg msg) { - if (msg.isDeleted()) { - msg.getDeletedKeys().forEach(key -> deviceAttributes.remove(key)); - } else { - deviceAttributes.update(msg.getScope(), msg.getValues()); - } + this.defaultMetaData = new TbMsgMetaData(); + this.defaultMetaData.putValue("deviceName", deviceName); + this.defaultMetaData.putValue("deviceType", deviceType); } - void processRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg) { + void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) { ToDeviceRpcRequest request = msg.getMsg(); ToDeviceRpcRequestBody body = request.getBody(); ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( @@ -118,9 +164,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso syncSessionSet.forEach(rpcSubscriptions::remove); if (request.isOneway() && sent) { - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(msg, (String) null); - context.parent().tell(responsePluginMsg, ActorRef.noSender()); logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null)); } else { registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); } @@ -132,24 +177,46 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } - private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestPluginMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) { - rpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); - TimeoutIntMsg timeoutMsg = new TimeoutIntMsg(rpcRequest.getRequestId(), timeout); + private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) { + toDeviceRpcPendingMap.put(rpcRequest.getRequestId(), new ToDeviceRpcRequestMetadata(msg, sent)); + DeviceActorServerSideRpcTimeoutMsg timeoutMsg = new DeviceActorServerSideRpcTimeoutMsg(rpcRequest.getRequestId(), timeout); scheduleMsgWithDelay(context, timeoutMsg, timeoutMsg.getTimeout()); } - public void processTimeout(ActorContext context, TimeoutMsg msg) { - ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(msg.getId()); + void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) { + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); if (requestMd != null) { logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION); - context.parent().tell(responsePluginMsg, ActorRef.noSender()); + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), + null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); + } + } + + void processQueueTimeout(ActorContext context, DeviceActorQueueTimeoutMsg msg) { + PendingSessionMsgData data = pendingMsgs.remove(msg.getId()); + if (data != null) { + logger.debug("[{}] Queue put [{}] timeout detected!", deviceId, msg.getId()); + ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(data.getSessionMsgType(), RuleEngineError.QUEUE_PUT_TIMEOUT); + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress()); + } + } + + void processQueueAck(ActorContext context, RuleEngineQueuePutAckMsg msg) { + PendingSessionMsgData data = pendingMsgs.remove(msg.getId()); + if (data != null && data.isReplyOnQueueAck()) { + int remainingAcks = data.getAckMsgCount() - 1; + data.setAckMsgCount(remainingAcks); + logger.debug("[{}] Queue put [{}] ack detected. Remaining acks: {}!", deviceId, msg.getId(), remainingAcks); + if (remainingAcks == 0) { + ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onSuccess(data.getSessionMsgType(), data.getRequestId()); + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServerAddress()); + } } } private void sendPendingRequests(ActorContext context, SessionId sessionId, SessionType type, Optional server) { - if (!rpcPendingMap.isEmpty()) { - logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, rpcPendingMap.size(), sessionId); + if (!toDeviceRpcPendingMap.isEmpty()) { + logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); if (type == SessionType.SYNC) { logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); rpcSubscriptions.remove(sessionId); @@ -159,12 +226,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } Set sentOneWayIds = new HashSet<>(); if (type == SessionType.ASYNC) { - rpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds)); + toDeviceRpcPendingMap.entrySet().forEach(processPendingRpc(context, sessionId, server, sentOneWayIds)); } else { - rpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds)); + toDeviceRpcPendingMap.entrySet().stream().findFirst().ifPresent(processPendingRpc(context, sessionId, server, sentOneWayIds)); } - sentOneWayIds.forEach(rpcPendingMap::remove); + sentOneWayIds.forEach(toDeviceRpcPendingMap::remove); } private Consumer> processPendingRpc(ActorContext context, SessionId sessionId, Optional server, Set sentOneWayIds) { @@ -173,8 +240,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso ToDeviceRpcRequestBody body = request.getBody(); if (request.isOneway()) { sentOneWayIds.add(entry.getKey()); - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(entry.getValue().getMsg(), (String) null); - context.parent().tell(responsePluginMsg, ActorRef.noSender()); + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(request.getId(), null, null)); } ToDeviceRpcRequestMsg rpcRequest = new ToDeviceRpcRequestMsg( entry.getKey(), @@ -186,14 +252,170 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso }; } - void process(ActorContext context, ToDeviceActorMsg msg) { + void process(ActorContext context, DeviceToDeviceActorMsg msg) { processSubscriptionCommands(context, msg); processRpcResponses(context, msg); processSessionStateMsgs(msg); + + SessionMsgType sessionMsgType = msg.getPayload().getMsgType(); + if (sessionMsgType.requiresRulesProcessing()) { + switch (sessionMsgType) { + case GET_ATTRIBUTES_REQUEST: + handleGetAttributesRequest(msg); + break; + case POST_ATTRIBUTES_REQUEST: + handlePostAttributesRequest(context, msg); + reportActivity(); + break; + case POST_TELEMETRY_REQUEST: + handlePostTelemetryRequest(context, msg); + reportActivity(); + break; + case TO_SERVER_RPC_REQUEST: + handleClientSideRPCRequest(context, msg); + reportActivity(); + break; + } + } + } + + private void reportActivity() { + systemContext.getDeviceStateService().onDeviceActivity(deviceId); + } + + private void reportSessionOpen() { + systemContext.getDeviceStateService().onDeviceConnect(deviceId); + } + + private void reportSessionClose() { + systemContext.getDeviceStateService().onDeviceDisconnect(deviceId); + } + + private void handleGetAttributesRequest(DeviceToDeviceActorMsg src) { + GetAttributesRequest request = (GetAttributesRequest) src.getPayload(); + ListenableFuture> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, request.getClientAttributeNames()); + ListenableFuture> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, request.getClientAttributeNames()); + + Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback>>() { + @Override + public void onSuccess(@Nullable List> result) { + BasicGetAttributesResponse response = BasicGetAttributesResponse.onSuccess(request.getMsgType(), + request.getRequestId(), BasicAttributeKVMsg.from(result.get(0), result.get(1))); + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, src.getSessionId()), src.getServerAddress()); + } + + @Override + public void onFailure(Throwable t) { + if (t instanceof Exception) { + ToDeviceMsg toDeviceMsg = BasicStatusCodeResponse.onError(SessionMsgType.GET_ATTRIBUTES_REQUEST, request.getRequestId(), (Exception) t); + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, src.getSessionId()), src.getServerAddress()); + } else { + logger.error("[{}] Failed to process attributes request", deviceId, t); + } + } + }); + } + + private ListenableFuture> getAttributeKvEntries(DeviceId deviceId, String scope, Optional> names) { + if (names.isPresent()) { + if (!names.get().isEmpty()) { + return systemContext.getAttributesService().find(deviceId, scope, names.get()); + } else { + return systemContext.getAttributesService().findAll(deviceId, scope); + } + } else { + return Futures.immediateFuture(Collections.emptyList()); + } + } + + private void handlePostAttributesRequest(ActorContext context, DeviceToDeviceActorMsg src) { + AttributesUpdateRequest request = (AttributesUpdateRequest) src.getPayload(); + + JsonObject json = new JsonObject(); + for (AttributeKvEntry kv : request.getAttributes()) { + kv.getBooleanValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); + kv.getLongValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); + kv.getDoubleValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); + kv.getStrValue().ifPresent(v -> json.addProperty(kv.getKey(), v)); + } + + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); + PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), + SessionMsgType.POST_ATTRIBUTES_REQUEST, request.getRequestId(), true, 1); + pushToRuleEngineWithTimeout(context, tbMsg, msgData); + } + + private void handlePostTelemetryRequest(ActorContext context, DeviceToDeviceActorMsg src) { + TelemetryUploadRequest request = (TelemetryUploadRequest) src.getPayload(); + + Map> tsData = request.getData(); + + PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), + SessionMsgType.POST_TELEMETRY_REQUEST, request.getRequestId(), true, tsData.size()); + + for (Map.Entry> entry : tsData.entrySet()) { + JsonObject json = new JsonObject(); + json.addProperty("ts", entry.getKey()); + JsonObject values = new JsonObject(); + for (KvEntry kv : entry.getValue()) { + kv.getBooleanValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); + kv.getLongValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); + kv.getDoubleValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); + kv.getStrValue().ifPresent(v -> values.addProperty(kv.getKey(), v)); + } + json.add("values", values); + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); + pushToRuleEngineWithTimeout(context, tbMsg, msgData); + } + } + + private void handleClientSideRPCRequest(ActorContext context, DeviceToDeviceActorMsg src) { + ToServerRpcRequestMsg request = (ToServerRpcRequestMsg) src.getPayload(); + + JsonObject json = new JsonObject(); + json.addProperty("method", request.getMethod()); + json.add("params", jsonParser.parse(request.getParams())); + + TbMsgMetaData requestMetaData = defaultMetaData.copy(); + requestMetaData.putValue("requestId", Integer.toString(request.getRequestId())); + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, requestMetaData, TbMsgDataType.JSON, gson.toJson(json), null, null, 0L); + PendingSessionMsgData msgData = new PendingSessionMsgData(src.getSessionId(), src.getServerAddress(), SessionMsgType.TO_SERVER_RPC_REQUEST, request.getRequestId(), false, 1); + pushToRuleEngineWithTimeout(context, tbMsg, msgData); + + scheduleMsgWithDelay(context, new DeviceActorClientSideRpcTimeoutMsg(request.getRequestId(), systemContext.getClientSideRpcTimeout()), systemContext.getClientSideRpcTimeout()); + toServerRpcPendingMap.put(request.getRequestId(), new ToServerRpcRequestMetadata(src.getSessionId(), src.getSessionType(), src.getServerAddress())); + } + + public void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) { + ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId()); + if (data != null) { + logger.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId()); + ToDeviceMsg toDeviceMsg = new RuleEngineErrorMsg(SessionMsgType.TO_SERVER_RPC_REQUEST, RuleEngineError.TIMEOUT); + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(toDeviceMsg, data.getSessionId()), data.getServer()); + } + } + + void processToServerRPCResponse(ActorContext context, ToServerRpcResponseActorMsg msg) { + ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getMsg().getRequestId()); + if (data != null) { + sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(msg.getMsg(), data.getSessionId()), data.getServer()); + } + } + + private void pushToRuleEngineWithTimeout(ActorContext context, TbMsg tbMsg, PendingSessionMsgData pendingMsgData) { + SessionMsgType sessionMsgType = pendingMsgData.getSessionMsgType(); + int requestId = pendingMsgData.getRequestId(); + if (systemContext.isQueuePersistenceEnabled()) { + pendingMsgs.put(tbMsg.getId(), pendingMsgData); + scheduleMsgWithDelay(context, new DeviceActorQueueTimeoutMsg(tbMsg.getId(), systemContext.getQueuePersistenceTimeout()), systemContext.getQueuePersistenceTimeout()); + } else { + ToDeviceSessionActorMsg response = new BasicToDeviceSessionActorMsg(BasicStatusCodeResponse.onSuccess(sessionMsgType, requestId), pendingMsgData.getSessionId()); + sendMsgToSessionActor(response, pendingMsgData.getServerAddress()); + } + context.parent().tell(new DeviceActorToRuleEngineMsg(context.self(), tbMsg), context.self()); } void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) { - refreshAttributes(msg); if (attributeSubscriptions.size() > 0) { ToDeviceMsg notification = null; if (msg.isDeleted()) { @@ -223,50 +445,29 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } } -// void process(ActorContext context, RuleChainDeviceMsg srcMsg) { -// ChainProcessingMetaData md = new ChainProcessingMetaData(srcMsg.getRuleChain(), -// srcMsg.getToDeviceActorMsg(), new DeviceMetaData(deviceId, deviceName, deviceType, deviceAttributes), context.self()); -// ChainProcessingContext ctx = new ChainProcessingContext(md); -// if (ctx.getChainLength() > 0) { -// RuleProcessingMsg msg = new RuleProcessingMsg(ctx); -// ActorRef ruleActorRef = ctx.getCurrentActor(); -// ruleActorRef.tell(msg, ActorRef.noSender()); -// } else { -// context.self().tell(new RulesProcessedMsg(ctx), context.self()); -// } -// } - - void processRpcResponses(ActorContext context, ToDeviceActorMsg msg) { + private void processRpcResponses(ActorContext context, DeviceToDeviceActorMsg msg) { SessionId sessionId = msg.getSessionId(); FromDeviceMsg inMsg = msg.getPayload(); - if (inMsg.getMsgType() == MsgType.TO_DEVICE_RPC_RESPONSE) { + if (inMsg.getMsgType() == SessionMsgType.TO_DEVICE_RPC_RESPONSE) { logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); ToDeviceRpcResponseMsg responseMsg = (ToDeviceRpcResponseMsg) inMsg; - ToDeviceRpcRequestMetadata requestMd = rpcPendingMap.remove(responseMsg.getRequestId()); + ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); boolean success = requestMd != null; if (success) { - ToPluginRpcResponseDeviceMsg responsePluginMsg = toPluginRpcResponseMsg(requestMd.getMsg(), responseMsg.getData()); - Optional pluginServerAddress = requestMd.getMsg().getServerAddress(); - if (pluginServerAddress.isPresent()) { - systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg); - logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); - } else { - context.parent().tell(responsePluginMsg, ActorRef.noSender()); - logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); - } + systemContext.getDeviceRpcService().process(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), responseMsg.getData(), null)); } else { logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); } if (msg.getSessionType() == SessionType.SYNC) { BasicCommandAckResponse response = success - ? BasicCommandAckResponse.onSuccess(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId()) - : BasicCommandAckResponse.onError(MsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException()); + ? BasicCommandAckResponse.onSuccess(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId()) + : BasicCommandAckResponse.onError(SessionMsgType.TO_DEVICE_RPC_REQUEST, responseMsg.getRequestId(), new TimeoutException()); sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(response, msg.getSessionId()), msg.getServerAddress()); } } } - public void processClusterEventMsg(ClusterEventMsg msg) { + void processClusterEventMsg(ClusterEventMsg msg) { if (!msg.isAdded()) { logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress()); Predicate> filter = e -> e.getValue().getServer() @@ -276,69 +477,43 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } } - private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data) { - return toPluginRpcResponseMsg(requestMsg, data, null); - } - - private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, RpcError error) { - return toPluginRpcResponseMsg(requestMsg, null, error); - } - - private ToPluginRpcResponseDeviceMsg toPluginRpcResponseMsg(ToDeviceRpcRequestPluginMsg requestMsg, String data, RpcError error) { - return new ToPluginRpcResponseDeviceMsg( - requestMsg.getPluginId(), - requestMsg.getPluginTenantId(), - new FromDeviceRpcResponse(requestMsg.getMsg().getId(), - data, - error - ) - ); - } - -// void onRulesProcessedMsg(ActorContext context, RulesProcessedMsg msg) { -// ChainProcessingContext ctx = msg.getCtx(); -// ToDeviceActorMsg inMsg = ctx.getInMsg(); -// SessionId sid = inMsg.getSessionId(); -// ToDeviceSessionActorMsg response; -// if (ctx.getResponse() != null) { -// response = new BasicToDeviceSessionActorMsg(ctx.getResponse(), sid); -// } else { -// response = new BasicToDeviceSessionActorMsg(ctx.getError(), sid); -// } -// sendMsgToSessionActor(response, inMsg.getServerAddress()); -// } - - private void processSubscriptionCommands(ActorContext context, ToDeviceActorMsg msg) { + private void processSubscriptionCommands(ActorContext context, DeviceToDeviceActorMsg msg) { SessionId sessionId = msg.getSessionId(); SessionType sessionType = msg.getSessionType(); FromDeviceMsg inMsg = msg.getPayload(); - if (inMsg.getMsgType() == MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) { + if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST) { logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); attributeSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); - } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) { + } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST) { logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); attributeSubscriptions.remove(sessionId); - } else if (inMsg.getMsgType() == MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) { + } else if (inMsg.getMsgType() == SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST) { logger.debug("[{}] Registering rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); rpcSubscriptions.put(sessionId, new SessionInfo(sessionType, msg.getServerAddress())); sendPendingRequests(context, sessionId, sessionType, msg.getServerAddress()); - } else if (inMsg.getMsgType() == MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) { + } else if (inMsg.getMsgType() == SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST) { logger.debug("[{}] Canceling rpc subscription for session [{}][{}]", deviceId, sessionId, sessionType); rpcSubscriptions.remove(sessionId); } } - private void processSessionStateMsgs(ToDeviceActorMsg msg) { + private void processSessionStateMsgs(DeviceToDeviceActorMsg msg) { SessionId sessionId = msg.getSessionId(); FromDeviceMsg inMsg = msg.getPayload(); if (inMsg instanceof SessionOpenMsg) { logger.debug("[{}] Processing new session [{}]", deviceId, sessionId); sessions.put(sessionId, new SessionInfo(SessionType.ASYNC, msg.getServerAddress())); + if (sessions.size() == 1) { + reportSessionOpen(); + } } else if (inMsg instanceof SessionCloseMsg) { logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); sessions.remove(sessionId); attributeSubscriptions.remove(sessionId); rpcSubscriptions.remove(sessionId); + if (sessions.isEmpty()) { + reportSessionClose(); + } } } @@ -352,17 +527,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso } } - private List fetchAttributes(String scope) { - try { - //TODO: replace this with async operation. Happens only during actor creation, but is still criticla for performance, - return systemContext.getAttributesService().findAll(this.deviceId, scope).get(); - } catch (InterruptedException | ExecutionException e) { - logger.warning("[{}] Failed to fetch attributes for scope: {}", deviceId, scope); - throw new RuntimeException(e); - } - } - - public void processCredentialsUpdate() { + void processCredentialsUpdate() { sessions.forEach((k, v) -> { sendMsgToSessionActor(new BasicToDeviceSessionActorMsg(new SessionCloseNotification(), k), v.getServer()); }); @@ -370,8 +535,12 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso rpcSubscriptions.clear(); } - public void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) { + void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) { this.deviceName = msg.getDeviceName(); this.deviceType = msg.getDeviceType(); + this.defaultMetaData = new TbMsgMetaData(); + this.defaultMetaData.putValue("deviceName", deviceName); + this.defaultMetaData.putValue("deviceType", deviceType); } + } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java new file mode 100644 index 0000000000..1426644bce --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorToRuleEngineMsg.java @@ -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.device; + +import akka.actor.ActorRef; +import lombok.Data; +import org.thingsboard.server.common.data.id.TenantId; +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; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java b/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java new file mode 100644 index 0000000000..23ad966049 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/PendingSessionMsgData.java @@ -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; + private final SessionMsgType sessionMsgType; + private final int requestId; + private final boolean replyOnQueueAck; + private int ackMsgCount; + +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java b/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java new file mode 100644 index 0000000000..0b6fe54c4a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/RuleEngineQueuePutAckMsg.java @@ -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; + } +} diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java index 01342fd866..8a4262c5b2 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/ToDeviceRpcRequestMetadata.java @@ -16,13 +16,13 @@ package org.thingsboard.server.actors.device; import lombok.Data; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; /** * @author Andrew Shvayka */ @Data public class ToDeviceRpcRequestMetadata { - private final ToDeviceRpcRequestPluginMsg msg; + private final ToDeviceRpcRequestActorMsg msg; private final boolean sent; } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java new file mode 100644 index 0000000000..f82a8c2481 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/ToServerRpcRequestMetadata.java @@ -0,0 +1,33 @@ +/** + * 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.data.id.SessionId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.session.SessionType; + +import java.util.Optional; + +/** + * @author Andrew Shvayka + */ +@Data +public class ToServerRpcRequestMetadata { + private final SessionId sessionId; + private final SessionType type; + private final Optional server; +} diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java index 88278f316d..7cb31610eb 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginActor.java @@ -26,7 +26,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbActorMsg; 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.common.msg.timeout.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; @@ -153,6 +153,7 @@ public class PluginActor extends ComponentActor } catch (Exception ex) { logger.debug("[{}] Failed to process RuleToPlugin msg: [{}] [{}]", tenantId, msg.getMsg(), ex); RuleToPluginMsg ruleMsg = msg.getMsg(); - MsgType responceMsgType = MsgType.RULE_ENGINE_ERROR; + SessionMsgType responceMsgType = SessionMsgType.RULE_ENGINE_ERROR; Integer requestId = 0; if (ruleMsg.getPayload() instanceof FromDeviceRequestMsg) { requestId = ((FromDeviceRequestMsg) ruleMsg.getPayload()).getRequestId(); @@ -216,7 +217,7 @@ public class PluginActorMessageProcessor extends ComponentMsgProcessor @Override public void onStop(ActorContext context) { onStop(); - scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay()); +// scheduleMsgWithDelay(context, new PluginTerminationMsg(entityId), systemContext.getPluginActorTerminationDelay()); } private void onStop() { diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java index 10f16ce8b1..5e16aabd32 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java @@ -36,9 +36,12 @@ 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.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleMetaData; import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.common.msg.timeout.TimeoutMsg; import org.thingsboard.server.extensions.api.device.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; import org.thingsboard.server.extensions.api.plugins.PluginCallback; @@ -348,7 +351,7 @@ public final class PluginProcessingContext implements PluginContext { throw new IllegalStateException("Not Implemented!"); } } else { - callback.onSuccess(this, ValidationResult.ok()); + callback.onSuccess(this, ValidationResult.ok(null)); } } @@ -366,7 +369,7 @@ public final class PluginProcessingContext implements PluginContext { } else if (ctx.isCustomerUser() && !device.getCustomerId().equals(ctx.getCustomerId())) { return ValidationResult.accessDenied("Device doesn't belong to the current Customer!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(null); } } })); @@ -387,7 +390,7 @@ public final class PluginProcessingContext implements PluginContext { } else if (ctx.isCustomerUser() && !asset.getCustomerId().equals(ctx.getCustomerId())) { return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(null); } } })); @@ -408,7 +411,7 @@ public final class PluginProcessingContext implements PluginContext { } else if (ctx.isSystemAdmin() && !rule.getTenantId().isNullUid()) { return ValidationResult.accessDenied("Rule is not in system scope!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(null); } } })); @@ -429,7 +432,7 @@ public final class PluginProcessingContext implements PluginContext { } else if (ctx.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) { return ValidationResult.accessDenied("Rule chain is not in system scope!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(null); } } })); @@ -451,7 +454,7 @@ public final class PluginProcessingContext implements PluginContext { } else if (ctx.isSystemAdmin() && !plugin.getTenantId().isNullUid()) { return ValidationResult.accessDenied("Plugin is not in system scope!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(null); } } })); @@ -472,7 +475,7 @@ public final class PluginProcessingContext implements PluginContext { } 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(); + return ValidationResult.ok(null); } } })); @@ -483,7 +486,7 @@ public final class PluginProcessingContext implements PluginContext { 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()); + callback.onSuccess(this, ValidationResult.ok(null)); } else { ListenableFuture tenantFuture = pluginCtx.tenantService.findTenantByIdAsync(new TenantId(entityId.getId())); Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { @@ -492,7 +495,7 @@ public final class PluginProcessingContext implements PluginContext { } else if (!tenant.getId().equals(ctx.getTenantId())) { return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(null); } })); } diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java index 182897025e..dcabedc88d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/SharedPluginProcessingContext.java @@ -18,13 +18,13 @@ 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.PluginId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.common.msg.timeout.TimeoutMsg; 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; @@ -37,9 +37,6 @@ 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; @@ -108,8 +105,8 @@ public final class SharedPluginProcessingContext { 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); +// ToDeviceRpcRequestPluginMsg rpcMsg = new ToDeviceRpcRequestPluginMsg(pluginId, tenantId, msg); +// forward(msg.getDeviceId(), rpcMsg, rpcService::tell); } private void forward(DeviceId deviceId, T msg, BiConsumer rpcFunction) { diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java index f135ffbd53..0319a3e4f9 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/ValidationResult.java @@ -20,29 +20,30 @@ import lombok.Data; @Data @AllArgsConstructor -public class ValidationResult { +public class ValidationResult { private final ValidationResultCode resultCode; private final String message; + private final V v; - public static ValidationResult ok() { - return new ValidationResult(ValidationResultCode.OK, "Ok"); + public static ValidationResult ok(V v) { + return new ValidationResult<>(ValidationResultCode.OK, "Ok", v); } - public static ValidationResult accessDenied(String message) { - return new ValidationResult(ValidationResultCode.ACCESS_DENIED, message); + public static ValidationResult accessDenied(String message) { + return new ValidationResult<>(ValidationResultCode.ACCESS_DENIED, message, null); } - public static ValidationResult entityNotFound(String message) { - return new ValidationResult(ValidationResultCode.ENTITY_NOT_FOUND, message); + public static ValidationResult entityNotFound(String message) { + return new ValidationResult<>(ValidationResultCode.ENTITY_NOT_FOUND, message, null); } - public static ValidationResult unauthorized(String message) { - return new ValidationResult(ValidationResultCode.UNAUTHORIZED, message); + public static ValidationResult unauthorized(String message) { + return new ValidationResult<>(ValidationResultCode.UNAUTHORIZED, message, null); } - public static ValidationResult internalError(String message) { - return new ValidationResult(ValidationResultCode.INTERNAL_ERROR, message); + public static ValidationResult internalError(String message) { + return new ValidationResult<>(ValidationResultCode.INTERNAL_ERROR, message, null); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java index 4fa222755d..bc36dc8098 100644 --- a/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java +++ b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java @@ -24,10 +24,12 @@ import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; import org.thingsboard.server.extensions.api.plugins.msg.*; import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; @@ -35,6 +37,7 @@ import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.service.cluster.rpc.GrpcSession; import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import java.io.Serializable; import java.util.UUID; @@ -83,7 +86,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener { @Override public void onToDeviceActorRpcMsg(GrpcSession session, ClusterAPIProtos.ToDeviceActorRpcMessage msg) { log.trace("{} session [{}] received device actor msg {}", getType(session), session.getRemoteServer(), msg); - service.onMsg((ToDeviceActorMsg) deserialize(msg.getData().toByteArray())); + service.onMsg((DeviceToDeviceActorMsg) deserialize(msg.getData().toByteArray())); } @Override @@ -139,28 +142,20 @@ public class BasicRpcSessionListener implements GrpcSessionListener { return new UUID(uid.getPluginUuidMsb(), uid.getPluginUuidLsb()); } - private static ToDeviceRpcRequestPluginMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) { - ClusterAPIProtos.PluginAddress address = msg.getAddress(); - TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId())); - PluginId pluginId = new PluginId(toUUID(address.getPluginId())); - + private static ToDeviceRpcRequestActorMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToDeviceRpcRequestRpcMessage msg) { TenantId deviceTenantId = new TenantId(toUUID(msg.getDeviceTenantId())); DeviceId deviceId = new DeviceId(toUUID(msg.getDeviceId())); ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(msg.getMethod(), msg.getParams()); - ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), null, deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); + ToDeviceRpcRequest request = new ToDeviceRpcRequest(toUUID(msg.getMsgId()), deviceTenantId, deviceId, msg.getOneway(), msg.getExpTime(), requestBody); - return new ToDeviceRpcRequestPluginMsg(serverAddress, pluginId, pluginTenantId, request); + return new ToDeviceRpcRequestActorMsg(serverAddress, request); } private static ToPluginRpcResponseDeviceMsg deserialize(ServerAddress serverAddress, ClusterAPIProtos.ToPluginRpcResponseRpcMessage msg) { - ClusterAPIProtos.PluginAddress address = msg.getAddress(); - TenantId pluginTenantId = new TenantId(toUUID(address.getTenantId())); - PluginId pluginId = new PluginId(toUUID(address.getPluginId())); - RpcError error = !StringUtils.isEmpty(msg.getError()) ? RpcError.valueOf(msg.getError()) : null; FromDeviceRpcResponse response = new FromDeviceRpcResponse(toUUID(msg.getMsgId()), msg.getResponse(), error); - return new ToPluginRpcResponseDeviceMsg(pluginId, pluginTenantId, response); + return new ToPluginRpcResponseDeviceMsg(null, null, response); } @SuppressWarnings("unchecked") diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 8ffd378da7..f6c45c0479 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -16,15 +16,20 @@ package org.thingsboard.server.actors.ruleChain; import akka.actor.ActorRef; -import akka.actor.Cancellable; +import com.datastax.driver.core.utils.UUIDs; import com.google.common.base.Function; import org.thingsboard.rule.engine.api.*; 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.cluster.ServerAddress; +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; @@ -41,13 +46,13 @@ import scala.concurrent.duration.Duration; import java.util.List; 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 static final Function, ? extends Void> LIST_VOID_FUNCTION = v -> null; private final ActorSystemContext mainCtx; private final RuleNodeCtx nodeCtx; @@ -63,8 +68,13 @@ class DefaultTbContext implements TbContext { @Override public void tellNext(TbMsg msg, String relationType) { + tellNext(msg, relationType, null); + } + + @Override + public void tellNext(TbMsg msg, String relationType, Throwable th) { if (nodeCtx.getSelf().isDebugMode()) { - mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg); + mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, th); } nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationType, msg), nodeCtx.getSelfActor()); } @@ -112,6 +122,16 @@ class DefaultTbContext implements TbContext { nodeCtx.setSelf(self); } + @Override + public TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { + return new TbMsg(UUIDs.timeBased(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L); + } + + @Override + public TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { + return new TbMsg(origMsg.getId(), type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId(), 0L); + } + @Override public RuleNodeId getSelfId() { return nodeCtx.getSelf().getId(); @@ -142,6 +162,11 @@ class DefaultTbContext implements TbContext { return mainCtx.getDbCallbackExecutor(); } + @Override + public ListeningExecutor getExternalCallExecutor() { + return mainCtx.getExternalCallExecutorService(); + } + @Override public ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames) { return new NashornJsEngine(script, functionName, argNames); @@ -206,4 +231,28 @@ class DefaultTbContext implements TbContext { public MailService getMailService() { return mainCtx.getMailService(); } + + @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 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().process(request, response -> { + consumer.accept(RuleEngineDeviceRpcResponse.builder() + .deviceId(src.getDeviceId()) + .requestId(src.getRequestId()) + .error(response.getError()) + .response(response.getResponse()) + .build()); + }); + } + }; + } } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java index f539e32a3c..4812002ec2 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java @@ -18,6 +18,7 @@ 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; @@ -44,9 +45,15 @@ public class RuleChainActor extends ComponentActor { + private static final long DEFAULT_CLUSTER_PARTITION = 0L; private final ActorRef parent; private final ActorRef self; private final Map nodeActors; @@ -58,6 +63,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList = service.getRuleChainNodes(entityId); - // Creating and starting the actors; + if (!started) { + RuleChain ruleChain = service.findRuleChainById(entityId); + List 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 ruleNodeList) { for (RuleNode ruleNode : ruleNodeList) { - ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); - nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); + for (TbMsg tbMsg : queue.findUnprocessed(ruleNode.getId().getId(), 0L)) { + pushMsgToNode(nodeActors.get(ruleNode.getId()), tbMsg); + } + } + if (firstNode != null) { + for (TbMsg tbMsg : queue.findUnprocessed(entityId.getId(), 0L)) { + pushMsgToNode(firstNode, tbMsg); + } } - initRoutes(ruleChain, ruleNodeList); } @Override @@ -105,6 +130,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor relations = service.getRuleNodeRelations(ruleNode.getId()); - 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() + "]"); + 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())); } - nodeRoutes.computeIfAbsent(ruleNode.getId(), k -> new ArrayList<>()) - .add(new RuleNodeRelation(ruleNode.getId(), relation.getTo(), relation.getType())); } } @@ -152,37 +183,81 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor pushMsgToNode(firstNode, msg)); + } + + void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg envelope) { + checkActive(); + putToQueue(enrichWithRuleChainId(envelope.getTbMsg()), msg -> { + pushMsgToNode(firstNode, msg); + envelope.getCallbackRef().tell(new RuleEngineQueuePutAckMsg(msg.getId()), self); + }); + } + + void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) { + checkActive(); + if (envelope.isEnqueue()) { + putToQueue(enrichWithRuleChainId(envelope.getMsg()), msg -> pushMsgToNode(firstNode, msg)); + } else { + pushMsgToNode(firstNode, envelope.getMsg()); + } } void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) { checkActive(); RuleNodeId originator = envelope.getOriginator(); String targetRelationType = envelope.getRelationType(); - List relations = nodeRoutes.get(originator); - if (relations == null) { - return; - } - boolean copy = relations.size() > 1; - for (RuleNodeRelation relation : relations) { - TbMsg msg = envelope.getMsg(); - if (copy) { - msg = msg.copy(); + List relations = nodeRoutes.get(originator).stream() + .filter(r -> targetRelationType == null || targetRelationType.equalsIgnoreCase(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(msg, ackId.getId(), msg.getClusterPartition()); + } else if (relationsCount == 1) { + for (RuleNodeRelation relation : relations) { + pushToTarget(msg, relation.getOut()); } - if (targetRelationType == null || targetRelationType.equalsIgnoreCase(relation.getType())) { - switch (relation.getOut().getEntityType()) { + } else { + for (RuleNodeRelation relation : relations) { + EntityId target = relation.getOut(); + switch (target.getEntityType()) { case RULE_NODE: - RuleNodeId targetRuleNodeId = new RuleNodeId(relation.getOut().getId()); - RuleNodeCtx targetRuleNode = nodeActors.get(targetRuleNodeId); - pushMsgToNode(targetRuleNode, msg); + enqueueAndForwardMsgCopyToNode(msg, target); break; case RULE_CHAIN: -// TODO: implement + enqueueAndForwardMsgCopyToChain(msg, target); 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(msg, ackId.getId(), msg.getClusterPartition()); + } + } + + private void enqueueAndForwardMsgCopyToChain(TbMsg msg, EntityId target) { + 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, true), self); + } + + private void enqueueAndForwardMsgCopyToNode(TbMsg msg, EntityId target) { + 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)); + } + + private void pushToTarget(TbMsg msg, EntityId target) { + switch (target.getEntityType()) { + case RULE_NODE: + pushMsgToNode(nodeActors.get(new RuleNodeId(target.getId())), msg); + break; + case RULE_CHAIN: + parent.tell(new RuleChainToRuleChainMsg(new RuleChainId(target.getId()), entityId, msg, false), self); + break; } } @@ -192,4 +267,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor pendingMap = new HashMap<>(); + private Map pendingMap = new HashMap<>(); private Optional currentTargetServer; private boolean subscribedToAttributeUpdates; private boolean subscribedToRpcCommands; @@ -53,7 +52,7 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor { toDeviceMsg(new SessionOpenMsg()).ifPresent(m -> forwardToAppActor(ctx, m)); firstMsg = false; } - ToDeviceActorMsg pendingMsg = toDeviceMsg(msg); + DeviceToDeviceActorMsg pendingMsg = toDeviceMsg(msg); FromDeviceMsg fromDeviceMsg = pendingMsg.getPayload(); switch (fromDeviceMsg.getMsgType()) { case POST_TELEMETRY_REQUEST: @@ -86,8 +85,8 @@ class ASyncMsgProcessor extends AbstractSessionActorMsgProcessor { @Override public void processToDeviceMsg(ActorContext context, ToDeviceMsg msg) { try { - if (msg.getMsgType() != MsgType.SESSION_CLOSE) { - switch (msg.getMsgType()) { + if (msg.getSessionMsgType() != SessionMsgType.SESSION_CLOSE) { + switch (msg.getSessionMsgType()) { case STATUS_CODE_RESPONSE: case GET_ATTRIBUTES_RESPONSE: ResponseMsg responseMsg = (ResponseMsg) msg; diff --git a/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java index 96526dda88..bbaef5d8e5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/session/AbstractSessionActorMsgProcessor.java @@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.SessionId; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.device.BasicToDeviceActorMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.BasicDeviceToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.*; import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; @@ -37,7 +37,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP protected final SessionId sessionId; protected SessionContext sessionCtx; - protected ToDeviceActorMsg toDeviceActorMsgPrototype; + protected DeviceToDeviceActorMsg deviceToDeviceActorMsgPrototype; protected AbstractSessionActorMsgProcessor(ActorSystemContext ctx, LoggingAdapter logger, SessionId sessionId) { super(ctx, logger); @@ -64,29 +64,29 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP protected void updateSessionCtx(ToDeviceActorSessionMsg msg, SessionType type) { sessionCtx = msg.getSessionMsg().getSessionContext(); - toDeviceActorMsgPrototype = new BasicToDeviceActorMsg(msg, type); + deviceToDeviceActorMsgPrototype = new BasicDeviceToDeviceActorMsg(msg, type); } - protected ToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) { + protected DeviceToDeviceActorMsg toDeviceMsg(ToDeviceActorSessionMsg msg) { AdaptorToSessionActorMsg adaptorMsg = msg.getSessionMsg(); - return new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, adaptorMsg.getMsg()); + return new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, adaptorMsg.getMsg()); } - protected Optional toDeviceMsg(FromDeviceMsg msg) { - if (toDeviceActorMsgPrototype != null) { - return Optional.of(new BasicToDeviceActorMsg(toDeviceActorMsgPrototype, msg)); + protected Optional toDeviceMsg(FromDeviceMsg msg) { + if (deviceToDeviceActorMsgPrototype != null) { + return Optional.of(new BasicDeviceToDeviceActorMsg(deviceToDeviceActorMsgPrototype, msg)); } else { return Optional.empty(); } } - protected Optional forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward) { + protected Optional forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward) { Optional address = systemContext.getRoutingService().resolveById(toForward.getDeviceId()); forwardToAppActor(ctx, toForward, address); return address; } - protected Optional forwardToAppActorIfAdressChanged(ActorContext ctx, ToDeviceActorMsg toForward, Optional oldAddress) { + protected Optional forwardToAppActorIfAdressChanged(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional oldAddress) { Optional newAddress = systemContext.getRoutingService().resolveById(toForward.getDeviceId()); if (!newAddress.equals(oldAddress)) { if (newAddress.isPresent()) { @@ -99,7 +99,7 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP return newAddress; } - protected void forwardToAppActor(ActorContext ctx, ToDeviceActorMsg toForward, Optional address) { + protected void forwardToAppActor(ActorContext ctx, DeviceToDeviceActorMsg toForward, Optional address) { if (address.isPresent()) { systemContext.getRpcService().tell(address.get(), toForward.toOtherAddress(systemContext.getRoutingService().getCurrentServer())); @@ -114,6 +114,6 @@ abstract class AbstractSessionActorMsgProcessor extends AbstractContextAwareMsgP } public DeviceId getDeviceId() { - return toDeviceActorMsgPrototype.getDeviceId(); + return deviceToDeviceActorMsgPrototype.getDeviceId(); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java index d69650347b..7f520b51e1 100644 --- a/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/session/SyncMsgProcessor.java @@ -20,7 +20,7 @@ import org.thingsboard.server.actors.shared.SessionTimeoutMsg; import org.thingsboard.server.common.data.id.SessionId; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.*; import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; import org.thingsboard.server.common.msg.session.ctrl.SessionCloseMsg; @@ -32,7 +32,7 @@ import akka.event.LoggingAdapter; import java.util.Optional; class SyncMsgProcessor extends AbstractSessionActorMsgProcessor { - private ToDeviceActorMsg pendingMsg; + private DeviceToDeviceActorMsg pendingMsg; private Optional currentTargetServer; private boolean pendingResponse; diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java index e25d3a7af9..4a7e072a7d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java @@ -17,22 +17,32 @@ package org.thingsboard.server.actors.shared; import akka.actor.ActorContext; import akka.event.LoggingAdapter; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.stats.StatsPersistTick; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; +import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; +import org.thingsboard.server.dao.queue.MsgQueue; -public abstract class ComponentMsgProcessor extends AbstractContextAwareMsgProcessor { +import javax.annotation.Nullable; +import java.util.function.Consumer; + +public abstract class ComponentMsgProcessor extends AbstractContextAwareMsgProcessor { protected final TenantId tenantId; protected final T entityId; + protected final MsgQueue queue; protected ComponentLifecycleState state; protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { super(systemContext, logger); this.tenantId = tenantId; this.entityId = id; + this.queue = systemContext.getMsgQueue(); } public abstract void start(ActorContext context) throws Exception; @@ -75,4 +85,19 @@ public abstract class ComponentMsgProcessor extends AbstractContextAwareMsgPr throw new IllegalStateException("Rule chain is not active!"); } } + + protected void putToQueue(final TbMsg tbMsg, final Consumer onSuccess) { + EntityId entityId = tbMsg.getRuleNodeId() != null ? tbMsg.getRuleNodeId() : tbMsg.getRuleChainId(); + Futures.addCallback(queue.put(tbMsg, entityId.getId(), 0), new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + onSuccess.accept(tbMsg); + } + + @Override + public void onFailure(Throwable t) { + logger.debug("Failed to push message [{}] to queue due to [{}]", tbMsg, t); + } + }); + } } diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java index d53c054414..10e247bdfc 100644 --- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java @@ -16,13 +16,16 @@ package org.thingsboard.server.actors.tenant; import akka.actor.ActorRef; +import akka.actor.OneForOneStrategy; import akka.actor.Props; -import akka.event.Logging; -import akka.event.LoggingAdapter; +import akka.actor.SupervisorStrategy; +import akka.japi.Function; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.device.DeviceActor; +import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; import org.thingsboard.server.actors.plugin.PluginTerminationMsg; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; +import org.thingsboard.server.actors.ruleChain.RuleChainToRuleChainMsg; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.plugin.TenantPluginManager; @@ -30,11 +33,13 @@ import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; +import scala.concurrent.duration.Duration; import java.util.HashMap; import java.util.Map; @@ -50,6 +55,12 @@ public class TenantActor extends RuleChainManagerActor { this.deviceActors = new HashMap<>(); } + + @Override + public SupervisorStrategy supervisorStrategy() { + return strategy; + } + @Override public void preStart() { logger.info("[{}] Starting tenant actor.", tenantId); @@ -64,63 +75,56 @@ public class TenantActor extends RuleChainManagerActor { @Override protected boolean process(TbActorMsg msg) { switch (msg.getMsgType()) { + case CLUSTER_EVENT_MSG: + broadcast(msg); + break; case COMPONENT_LIFE_CYCLE_MSG: onComponentLifecycleMsg((ComponentLifecycleMsg) msg); break; case SERVICE_TO_RULE_ENGINE_MSG: onServiceToRuleEngineMsg((ServiceToRuleEngineMsg) msg); break; + case DEVICE_ACTOR_TO_RULE_ENGINE_MSG: + onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg); + break; + case DEVICE_SESSION_TO_DEVICE_ACTOR_MSG: + case DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG: + case DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG: + case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG: + case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG: + case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG: + onToDeviceActorMsg((DeviceAwareMsg) msg); + break; + case RULE_CHAIN_TO_RULE_CHAIN_MSG: + onRuleChainMsg((RuleChainToRuleChainMsg) msg); + break; default: return false; } return true; } + @Override + protected void broadcast(Object msg) { + super.broadcast(msg); + deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); + } + private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { ruleChainManager.getRootChainActor().tell(msg, self()); } - -// @Override -// public void onReceive(Object msg) throws Exception { -// logger.debug("[{}] Received message: {}", tenantId, msg); -// if (msg instanceof ToDeviceActorMsg) { -// onToDeviceActorMsg((ToDeviceActorMsg) msg); -// } else if (msg instanceof ToPluginActorMsg) { -// onToPluginMsg((ToPluginActorMsg) msg); -// } else if (msg instanceof ToDeviceActorNotificationMsg) { -// onToDeviceActorMsg((ToDeviceActorNotificationMsg) msg); -// } else if (msg instanceof ClusterEventMsg) { -// broadcast(msg); -// } else if (msg instanceof ComponentLifecycleMsg) { -// onComponentLifecycleMsg((ComponentLifecycleMsg) msg); -// } else if (msg instanceof PluginTerminationMsg) { -// onPluginTerminated((PluginTerminationMsg) msg); -// } else { -// logger.warning("[{}] Unknown message: {}!", tenantId, msg); -// } -// } - - private void broadcast(Object msg) { - pluginManager.broadcast(msg); - deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); + private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) { + ruleChainManager.getRootChainActor().tell(msg, self()); } - private void onToDeviceActorMsg(ToDeviceActorMsg msg) { - getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); + private void onRuleChainMsg(RuleChainToRuleChainMsg msg) { + ruleChainManager.getOrCreateActor(context(), msg.getTarget()).tell(msg, self()); } - private void onToDeviceActorMsg(ToDeviceActorNotificationMsg msg) { - getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); - } - private void onToPluginMsg(ToPluginActorMsg msg) { - if (msg.getPluginTenantId().equals(tenantId)) { - ActorRef pluginActor = pluginManager.getOrCreateActor(this.context(), msg.getPluginId()); - pluginActor.tell(msg, ActorRef.noSender()); - } else { - context().parent().tell(msg, ActorRef.noSender()); - } + private void onToDeviceActorMsg(DeviceAwareMsg msg) { + getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender()); } private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { @@ -132,11 +136,6 @@ public class TenantActor extends RuleChainManagerActor { } } - private void onPluginTerminated(PluginTerminationMsg msg) { - pluginManager.remove(msg.getId()); - } - - private ActorRef getOrCreateDeviceActor(DeviceId deviceId) { return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId)) .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString())); @@ -158,4 +157,12 @@ public class TenantActor extends RuleChainManagerActor { } } + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function() { + @Override + public SupervisorStrategy.Directive apply(Throwable t) { + logger.error(t, "Unknown failure"); + return SupervisorStrategy.resume(); + } + }); + } diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 29436c1500..a35e5dcc3c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -63,6 +63,7 @@ import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.state.DeviceStateService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletRequest; @@ -137,6 +138,9 @@ public abstract class BaseController { @Autowired protected DeviceOfflineService offlineService; + @Autowired + protected DeviceStateService deviceStateService; + @ExceptionHandler(ThingsboardException.class) public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { errorResponseHandler.handle(ex, response); @@ -606,7 +610,7 @@ public abstract class BaseController { auditLogService.logEntityAction(user.getTenantId(), customerId, user.getId(), user.getName(), entityId, entity, actionType, e, additionalInfo); } - protected static Exception toException(Throwable error) { + public static Exception toException(Throwable error) { return Exception.class.isInstance(error) ? (Exception) error : new Exception(error); } diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index f97603e40f..5322b2c017 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -90,6 +90,11 @@ public class DeviceController extends BaseController { savedDevice.getCustomerId(), device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); + if (device.getId() == null) { + deviceStateService.onDeviceAdded(savedDevice); + } else { + deviceStateService.onDeviceUpdated(savedDevice); + } return savedDevice; } catch (Exception e) { logEntityAction(emptyId(EntityType.DEVICE), device, @@ -112,6 +117,7 @@ public class DeviceController extends BaseController { device.getCustomerId(), ActionType.DELETED, null, strDeviceId); + deviceStateService.onDeviceDeleted(device); } catch (Exception e) { logEntityAction(emptyId(EntityType.DEVICE), null, @@ -387,7 +393,7 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/device/online", method = RequestMethod.GET) @ResponseBody public List getOnlineDevices(@RequestParam("contactType") DeviceStatusQuery.ContactType contactType, - @RequestParam("threshold") long threshold) throws ThingsboardException { + @RequestParam("threshold") long threshold) throws ThingsboardException { try { TenantId tenantId = getCurrentUser().getTenantId(); ListenableFuture> offlineDevices = offlineService.findOnlineDevices(tenantId.getId(), contactType, threshold); diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java new file mode 100644 index 0000000000..28261be14c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java @@ -0,0 +1,225 @@ +/** + * 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.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.util.concurrent.FutureCallback; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +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.data.id.UUIDBased; +import org.thingsboard.server.common.data.rpc.RpcRequest; +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity; +import org.thingsboard.server.extensions.api.plugins.PluginConstants; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.RpcError; +import org.thingsboard.server.service.rpc.DeviceRpcService; +import org.thingsboard.server.service.rpc.LocalRequestMetaData; +import org.thingsboard.server.service.security.AccessValidator; +import org.thingsboard.server.service.security.model.SecurityUser; + +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.function.Consumer; + +/** + * Created by ashvayka on 22.03.18. + */ +@RestController +@RequestMapping(PluginConstants.RPC_URL_PREFIX) +@Slf4j +public class RpcController extends BaseController { + + public static final int DEFAULT_TIMEOUT = 10000; + protected final ObjectMapper jsonMapper = new ObjectMapper(); + + @Autowired + private DeviceRpcService deviceRpcService; + + @Autowired + private AccessValidator accessValidator; + + private ExecutorService executor; + + @PostConstruct + public void initExecutor() { + executor = Executors.newSingleThreadExecutor(); + } + + @PreDestroy + public void shutdownExecutor() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) + @ResponseBody + public DeferredResult handleOneWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { + return handleDeviceRPCRequest(true, new DeviceId(UUID.fromString(deviceIdStr)), requestBody); + } + + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/twoway/{deviceId}", method = RequestMethod.POST) + @ResponseBody + public DeferredResult handleTwoWayDeviceRPCRequest(@PathVariable("deviceId") String deviceIdStr, @RequestBody String requestBody) throws ThingsboardException { + return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody); + } + + + private DeferredResult handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { + try { + JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); + RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(), + jsonMapper.writeValueAsString(rpcRequestBody.get("params"))); + + if (rpcRequestBody.has("timeout")) { + cmd.setTimeout(rpcRequestBody.get("timeout").asLong()); + } + SecurityUser currentUser = getCurrentUser(); + TenantId tenantId = currentUser.getTenantId(); + final DeferredResult response = new DeferredResult<>(); + long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT); + ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); + accessValidator.validate(currentUser, deviceId, new HttpValidationCallback(response, new FutureCallback>() { + @Override + public void onSuccess(@Nullable DeferredResult result) { + ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), + tenantId, + deviceId, + oneWay, + timeout, + body + ); + deviceRpcService.process(rpcRequest, new Consumer(){ + + @Override + public void accept(FromDeviceRpcResponse fromDeviceRpcResponse) { + reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse); + } + }); + } + + @Override + public void onFailure(Throwable e) { + ResponseEntity entity; + if (e instanceof ToErrorResponseEntity) { + entity = ((ToErrorResponseEntity) e).toErrorResponseEntity(); + } else { + entity = new ResponseEntity(HttpStatus.UNAUTHORIZED); + } + logRpcCall(currentUser, deviceId, body, oneWay, Optional.empty(), e); + response.setResult(entity); + } + })); + return response; + } catch (IOException ioe) { + throw new ThingsboardException("Invalid request body", ioe, ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } + + public void reply(LocalRequestMetaData rpcRequest, FromDeviceRpcResponse response) { + Optional rpcError = response.getError(); + DeferredResult responseWriter = rpcRequest.getResponseWriter(); + if (rpcError.isPresent()) { + logRpcCall(rpcRequest, rpcError, null); + RpcError error = rpcError.get(); + switch (error) { + case TIMEOUT: + responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); + break; + case NO_ACTIVE_CONNECTION: + responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT)); + break; + default: + responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); + break; + } + } else { + Optional responseData = response.getResponse(); + if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) { + String data = responseData.get(); + try { + logRpcCall(rpcRequest, rpcError, null); + responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); + } catch (IOException e) { + log.debug("Failed to decode device response: {}", data, e); + logRpcCall(rpcRequest, rpcError, e); + responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); + } + } else { + logRpcCall(rpcRequest, rpcError, null); + responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK)); + } + } + } + + private void logRpcCall(LocalRequestMetaData rpcRequest, Optional rpcError, Throwable e) { + logRpcCall(rpcRequest.getUser(), rpcRequest.getRequest().getDeviceId(), rpcRequest.getRequest().getBody(), rpcRequest.getRequest().isOneway(), rpcError, null); + } + + + private void logRpcCall(SecurityUser user, EntityId entityId, ToDeviceRpcRequestBody body, boolean oneWay, Optional rpcError, Throwable e) { + String rpcErrorStr = ""; + if (rpcError.isPresent()) { + rpcErrorStr = "RPC Error: " + rpcError.get().name(); + } + String method = body.getMethod(); + String params = body.getParams(); + + auditLogService.logEntityAction( + user.getTenantId(), + user.getCustomerId(), + user.getId(), + user.getName(), + (UUIDBased & EntityId) entityId, + null, + ActionType.RPC_CALL, + BaseController.toException(e), + rpcErrorStr, + oneWay, + method, + params); + } + + +} diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 48c9cd5342..05e7836786 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -237,7 +237,7 @@ public class RuleChainController extends BaseController { ScriptEngine engine = null; try { engine = new NashornJsEngine(script, functionName, argNames); - TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data); + TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L); switch (scriptType) { case "update": output = msgToOutput(engine.executeUpdate(inMsg)); diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index bf49074f91..1a7c116560 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -15,21 +15,34 @@ */ package org.thingsboard.server.controller; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TextPageData; import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.service.install.InstallScripts; @RestController @RequestMapping("/api") +@Slf4j public class TenantController extends BaseController { - + + @Autowired + private InstallScripts installScripts; + @Autowired private TenantService tenantService; @@ -49,10 +62,15 @@ public class TenantController extends BaseController { @PreAuthorize("hasAuthority('SYS_ADMIN')") @RequestMapping(value = "/tenant", method = RequestMethod.POST) - @ResponseBody + @ResponseBody public Tenant saveTenant(@RequestBody Tenant tenant) throws ThingsboardException { try { - return checkNotNull(tenantService.saveTenant(tenant)); + boolean newTenant = tenant.getId() == null; + tenant = checkNotNull(tenantService.saveTenant(tenant)); + if (newTenant) { + installScripts.createDefaultRuleChains(tenant.getId()); + } + return tenant; } catch (Exception e) { throw handleException(e); } @@ -72,7 +90,7 @@ public class TenantController extends BaseController { } @PreAuthorize("hasAuthority('SYS_ADMIN')") - @RequestMapping(value = "/tenants", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/tenants", params = {"limit"}, method = RequestMethod.GET) @ResponseBody public TextPageData getTenants(@RequestParam int limit, @RequestParam(required = false) String textSearch, @@ -85,5 +103,5 @@ public class TenantController extends BaseController { throw handleException(e); } } - + } diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 1835a937e7..0b23a89718 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -23,9 +23,7 @@ import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.service.install.DatabaseSchemaService; -import org.thingsboard.server.service.install.DatabaseUpgradeService; -import org.thingsboard.server.service.install.SystemDataLoaderService; +import org.thingsboard.server.service.install.*; import java.nio.file.Paths; @@ -40,9 +38,6 @@ public class ThingsboardInstallService { @Value("${install.upgrade.from_version:1.2.3}") private String upgradeFromVersion; - @Value("${install.data_dir}") - private String dataDir; - @Value("${install.load_demo:false}") private Boolean loadDemo; @@ -61,6 +56,9 @@ public class ThingsboardInstallService { @Autowired private SystemDataLoaderService systemDataLoaderService; + @Autowired + private DataUpdateService dataUpdateService; + public void performInstall() { try { if (isUpgrade) { @@ -87,6 +85,8 @@ public class ThingsboardInstallService { databaseUpgradeService.upgradeDatabase("1.4.0"); + dataUpdateService.updateData("1.4.0"); + log.info("Updating system data..."); systemDataLoaderService.deleteSystemWidgetBundle("charts"); @@ -113,13 +113,6 @@ public class ThingsboardInstallService { log.info("Starting ThingsBoard Installation..."); - if (this.dataDir == null) { - throw new RuntimeException("'install.data_dir' property should specified!"); - } - if (!Paths.get(this.dataDir).toFile().isDirectory()) { - throw new RuntimeException("'install.data_dir' property value is not a valid directory!"); - } - log.info("Installing DataBase schema..."); databaseSchemaService.createDatabaseSchema(); @@ -131,8 +124,8 @@ public class ThingsboardInstallService { systemDataLoaderService.createSysAdmin(); systemDataLoaderService.createAdminSettings(); systemDataLoaderService.loadSystemWidgets(); - systemDataLoaderService.loadSystemPlugins(); - systemDataLoaderService.loadSystemRules(); +// systemDataLoaderService.loadSystemPlugins(); +// systemDataLoaderService.loadSystemRules(); if (loadDemo) { log.info("Loading demo data..."); diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java index 5a6b30735e..27334c6601 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterGrpcService.java @@ -19,7 +19,6 @@ import com.google.protobuf.ByteString; import io.grpc.Server; import io.grpc.ServerBuilder; import io.grpc.stub.StreamObserver; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -27,29 +26,24 @@ import org.springframework.util.SerializationUtils; import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; -import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.gen.cluster.ClusterRpcServiceGrpc; -import org.thingsboard.server.service.cluster.discovery.DiscoveryService; import org.thingsboard.server.service.cluster.discovery.ServerInstance; import org.thingsboard.server.service.cluster.discovery.ServerInstanceService; -import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; -import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -124,7 +118,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI } @Override - public void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward) { + public void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward) { ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() .setToDeviceActorRpcMsg(toProtoMsg(toForward)).build(); tell(serverAddress, msg); @@ -138,7 +132,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI } @Override - public void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward) { + public void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward) { ClusterAPIProtos.ToRpcServerMessage msg = ClusterAPIProtos.ToRpcServerMessage.newBuilder() .setToDeviceRpcRequestRpcMsg(toProtoMsg(toForward)).build(); tell(serverAddress, msg); @@ -190,7 +184,7 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI } } - private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(ToDeviceActorMsg msg) { + private static ClusterAPIProtos.ToDeviceActorRpcMessage toProtoMsg(DeviceToDeviceActorMsg msg) { return ClusterAPIProtos.ToDeviceActorRpcMessage.newBuilder().setData( ByteString.copyFrom(SerializationUtils.serialize(msg)) ).build(); @@ -202,15 +196,10 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI ).build(); } - private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestPluginMsg msg) { + private static ClusterAPIProtos.ToDeviceRpcRequestRpcMessage toProtoMsg(ToDeviceRpcRequestActorMsg msg) { ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.Builder builder = ClusterAPIProtos.ToDeviceRpcRequestRpcMessage.newBuilder(); ToDeviceRpcRequest request = msg.getMsg(); - builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder() - .setTenantId(toUid(msg.getPluginTenantId().getId())) - .setPluginId(toUid(msg.getPluginId().getId())) - .build()); - builder.setDeviceTenantId(toUid(msg.getTenantId())); builder.setDeviceId(toUid(msg.getDeviceId())); @@ -227,11 +216,6 @@ public class ClusterGrpcService extends ClusterRpcServiceGrpc.ClusterRpcServiceI ClusterAPIProtos.ToPluginRpcResponseRpcMessage.Builder builder = ClusterAPIProtos.ToPluginRpcResponseRpcMessage.newBuilder(); FromDeviceRpcResponse request = msg.getResponse(); - builder.setAddress(ClusterAPIProtos.PluginAddress.newBuilder() - .setTenantId(toUid(msg.getPluginTenantId().getId())) - .setPluginId(toUid(msg.getPluginId().getId())) - .build()); - builder.setMsgId(toUid(request.getId())); request.getResponse().ifPresent(builder::setResponse); request.getError().ifPresent(e -> builder.setError(e.name())); diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java index 8c50bb7207..6aefe46a63 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/ClusterRpcService.java @@ -19,12 +19,12 @@ import io.grpc.stub.StreamObserver; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; +import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import java.util.UUID; @@ -35,13 +35,13 @@ public interface ClusterRpcService { void init(RpcMsgListener listener); - void tell(ServerAddress serverAddress, ToDeviceActorMsg toForward); + void tell(ServerAddress serverAddress, DeviceToDeviceActorMsg toForward); void tell(ServerAddress serverAddress, ToDeviceSessionActorMsg toForward); void tell(ServerAddress serverAddress, ToDeviceActorNotificationMsg toForward); - void tell(ServerAddress serverAddress, ToDeviceRpcRequestPluginMsg toForward); + void tell(ServerAddress serverAddress, ToDeviceRpcRequestActorMsg toForward); void tell(ServerAddress serverAddress, ToPluginRpcResponseDeviceMsg toForward); @@ -50,4 +50,5 @@ public interface ClusterRpcService { void broadcast(ToAllNodesMsg msg); void onSessionCreated(UUID msgUid, StreamObserver inputStream); + } diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java index a5c31513be..5d26fae1c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/RpcMsgListener.java @@ -20,18 +20,16 @@ import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; import org.thingsboard.server.actors.rpc.RpcSessionTellMsg; import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg; import org.thingsboard.server.common.msg.core.ToDeviceSessionActorMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; import org.thingsboard.server.extensions.api.plugins.msg.ToPluginActorMsg; -import org.thingsboard.server.extensions.api.plugins.rpc.PluginRpcMsg; -import org.thingsboard.server.gen.cluster.ClusterAPIProtos; /** * @author Andrew Shvayka */ public interface RpcMsgListener { - void onMsg(ToDeviceActorMsg msg); + void onMsg(DeviceToDeviceActorMsg msg); void onMsg(ToDeviceActorNotificationMsg msg); diff --git a/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java new file mode 100644 index 0000000000..3f6d87e41e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/executors/ExternalCallExecutorService.java @@ -0,0 +1,33 @@ +/** + * 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.service.executors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class ExternalCallExecutorService extends AbstractListeningExecutor { + + @Value("${actors.rule.external_call_thread_pool_size}") + private int externalCallExecutorThreadPoolSize; + + @Override + protected int getThreadPollSize() { + return externalCallExecutorThreadPoolSize; + } + +} + diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java index 5bdc0a7451..8478021f32 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseSchemaService.java @@ -37,17 +37,16 @@ public class CassandraDatabaseSchemaService implements DatabaseSchemaService { private static final String CASSANDRA_DIR = "cassandra"; private static final String SCHEMA_CQL = "schema.cql"; - @Value("${install.data_dir}") - private String dataDir; - @Autowired private CassandraInstallCluster cluster; + @Autowired + private InstallScripts installScripts; + @Override public void createDatabaseSchema() throws Exception { log.info("Installing Cassandra DataBase schema..."); - - Path schemaFile = Paths.get(this.dataDir, CASSANDRA_DIR, SCHEMA_CQL); + Path schemaFile = Paths.get(installScripts.getDataDir(), CASSANDRA_DIR, SCHEMA_CQL); loadCql(schemaFile); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java index 8dd969839d..e8891269db 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java @@ -43,9 +43,6 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { private static final String SCHEMA_UPDATE_CQL = "schema_update.cql"; - @Value("${install.data_dir}") - private String dataDir; - @Autowired private CassandraCluster cluster; @@ -55,6 +52,9 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { @Autowired private DashboardService dashboardService; + @Autowired + private InstallScripts installScripts; + @Override public void upgradeDatabase(String fromVersion) throws Exception { @@ -91,7 +91,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Relations dumped."); log.info("Updating schema ..."); - Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.0", SCHEMA_UPDATE_CQL); + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.0", SCHEMA_UPDATE_CQL); loadCql(schemaUpdateFile); log.info("Schema updated."); @@ -173,7 +173,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_CQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_CQL); loadCql(schemaUpdateFile); log.info("Schema updated."); @@ -189,7 +189,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService { case "1.4.0": log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_CQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_CQL); loadCql(schemaUpdateFile); log.info("Schema updated."); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java new file mode 100644 index 0000000000..94273a25de --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/DataUpdateService.java @@ -0,0 +1,22 @@ +/** + * 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.service.install; + +public interface DataUpdateService { + + void updateData(String fromVersion) throws Exception; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java new file mode 100644 index 0000000000..37e5b30d11 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultDataUpdateService.java @@ -0,0 +1,106 @@ +/** + * 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.service.install; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.IdBased; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.tenant.TenantService; + +import java.util.List; +import java.util.UUID; + +@Service +@Profile("install") +@Slf4j +public class DefaultDataUpdateService implements DataUpdateService { + + @Autowired + private TenantService tenantService; + + @Autowired + private RuleChainService ruleChainService; + + @Autowired + private InstallScripts installScripts; + + @Override + public void updateData(String fromVersion) throws Exception { + switch (fromVersion) { + case "1.4.0": + log.info("Updating data from version 1.4.0 to 1.5.0 ..."); + tenantsDefaultRuleChainUpdater.updateEntities(null); + break; + default: + throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); + } + } + + private PaginatedUpdater tenantsDefaultRuleChainUpdater = + new PaginatedUpdater() { + + @Override + protected List findEntities(String region, TextPageLink pageLink) { + return tenantService.findTenants(pageLink).getData(); + } + + @Override + protected void updateEntity(Tenant tenant) { + try { + RuleChain ruleChain = ruleChainService.getRootTenantRuleChain(tenant.getId()); + if (ruleChain == null) { + installScripts.createDefaultRuleChains(tenant.getId()); + } + } catch (Exception e) { + log.error("Unable to update Tenant", e); + } + } + }; + + public abstract class PaginatedUpdater> { + + private static final int DEFAULT_LIMIT = 100; + + public void updateEntities(I id) { + TextPageLink pageLink = new TextPageLink(DEFAULT_LIMIT); + boolean hasNext = true; + while (hasNext) { + List entities = findEntities(id, pageLink); + for (D entity : entities) { + updateEntity(entity); + } + hasNext = entities.size() == pageLink.getLimit(); + if (hasNext) { + int index = entities.size() - 1; + UUID idOffset = entities.get(index).getUuidId(); + pageLink.setIdOffset(idOffset); + } + } + } + + protected abstract List findEntities(I id, TextPageLink pageLink); + + protected abstract void updateEntity(D entity); + + } + +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 908faf2315..e33c2e3940 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -60,21 +60,12 @@ import java.nio.file.Paths; @Slf4j public class DefaultSystemDataLoaderService implements SystemDataLoaderService { - private static final String JSON_DIR = "json"; - private static final String SYSTEM_DIR = "system"; - private static final String DEMO_DIR = "demo"; - private static final String WIDGET_BUNDLES_DIR = "widget_bundles"; - private static final String PLUGINS_DIR = "plugins"; - private static final String RULES_DIR = "rules"; - private static final String DASHBOARDS_DIR = "dashboards"; - private static final ObjectMapper objectMapper = new ObjectMapper(); - public static final String JSON_EXT = ".json"; public static final String CUSTOMER_CRED = "customer"; public static final String DEFAULT_DEVICE_TYPE = "default"; - @Value("${install.data_dir}") - private String dataDir; + @Autowired + private InstallScripts installScripts; @Autowired private BCryptPasswordEncoder passwordEncoder; @@ -88,15 +79,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private WidgetsBundleService widgetsBundleService; - @Autowired - private WidgetTypeService widgetTypeService; - - @Autowired - private PluginService pluginService; - - @Autowired - private RuleService ruleService; - @Autowired private TenantService tenantService; @@ -109,9 +91,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private DeviceCredentialsService deviceCredentialsService; - @Autowired - private DashboardService dashboardService; - @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -146,56 +125,13 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { adminSettingsService.saveAdminSettings(mailSettings); } - @Override - public void loadSystemWidgets() throws Exception { - Path widgetBundlesDir = Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR); - try (DirectoryStream dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) { - dirStream.forEach( - path -> { - try { - JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile()); - JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle"); - WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class); - WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes"); - widgetTypesArrayJson.forEach( - widgetTypeJson -> { - try { - WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class); - widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); - widgetTypeService.saveWidgetType(widgetType); - } catch (Exception e) { - log.error("Unable to load widget type from json: [{}]", path.toString()); - throw new RuntimeException("Unable to load widget type from json", e); - } - } - ); - } catch (Exception e) { - log.error("Unable to load widgets bundle from json: [{}]", path.toString()); - throw new RuntimeException("Unable to load widgets bundle from json", e); - } - } - ); - } - } - - @Override - public void loadSystemPlugins() throws Exception { - loadPlugins(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, PLUGINS_DIR), null); - } - - - @Override - public void loadSystemRules() throws Exception { -// loadRules(Paths.get(dataDir, JSON_DIR, SYSTEM_DIR, RULES_DIR), null); - } - @Override public void loadDemoData() throws Exception { Tenant demoTenant = new Tenant(); demoTenant.setRegion("Global"); demoTenant.setTitle("Tenant"); demoTenant = tenantService.saveTenant(demoTenant); + installScripts.createDefaultRuleChains(demoTenant.getId()); createUser(Authority.TENANT_ADMIN, demoTenant.getId(), null, "tenant@thingsboard.org", "tenant"); Customer customerA = new Customer(); @@ -227,9 +163,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { createDevice(demoTenant.getId(), null, DEFAULT_DEVICE_TYPE, "Raspberry Pi Demo Device", "RASPBERRY_PI_DEMO_TOKEN", "Demo device that is used in " + "Raspberry Pi GPIO control sample application"); - loadPlugins(Paths.get(dataDir, JSON_DIR, DEMO_DIR, PLUGINS_DIR), demoTenant.getId()); -// loadRules(Paths.get(dataDir, JSON_DIR, DEMO_DIR, RULES_DIR), demoTenant.getId()); - loadDashboards(Paths.get(dataDir, JSON_DIR, DEMO_DIR, DASHBOARDS_DIR), demoTenant.getId(), null); + installScripts.loadDashboards(demoTenant.getId(), null); } @Override @@ -240,6 +174,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { } } + @Override + public void loadSystemWidgets() throws Exception { + installScripts.loadSystemWidgets(); + } + private User createUser(Authority authority, TenantId tenantId, CustomerId customerId, @@ -282,72 +221,4 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { return device; } - private void loadPlugins(Path pluginsDir, TenantId tenantId) throws Exception{ - try (DirectoryStream dirStream = Files.newDirectoryStream(pluginsDir, path -> path.toString().endsWith(JSON_EXT))) { - dirStream.forEach( - path -> { - try { - JsonNode pluginJson = objectMapper.readTree(path.toFile()); - PluginMetaData plugin = objectMapper.treeToValue(pluginJson, PluginMetaData.class); - plugin.setTenantId(tenantId); - if (plugin.getState() == ComponentLifecycleState.ACTIVE) { - plugin.setState(ComponentLifecycleState.SUSPENDED); - PluginMetaData savedPlugin = pluginService.savePlugin(plugin); - pluginService.activatePluginById(savedPlugin.getId()); - } else { - pluginService.savePlugin(plugin); - } - } catch (Exception e) { - log.error("Unable to load plugin from json: [{}]", path.toString()); - throw new RuntimeException("Unable to load plugin from json", e); - } - } - ); - } - } - - private void loadRules(Path rulesDir, TenantId tenantId) throws Exception { - try (DirectoryStream dirStream = Files.newDirectoryStream(rulesDir, path -> path.toString().endsWith(JSON_EXT))) { - dirStream.forEach( - path -> { - try { - JsonNode ruleJson = objectMapper.readTree(path.toFile()); - RuleMetaData rule = objectMapper.treeToValue(ruleJson, RuleMetaData.class); - rule.setTenantId(tenantId); - if (rule.getState() == ComponentLifecycleState.ACTIVE) { - rule.setState(ComponentLifecycleState.SUSPENDED); - RuleMetaData savedRule = ruleService.saveRule(rule); - ruleService.activateRuleById(savedRule.getId()); - } else { - ruleService.saveRule(rule); - } - } catch (Exception e) { - log.error("Unable to load rule from json: [{}]", path.toString()); - throw new RuntimeException("Unable to load rule from json", e); - } - } - ); - } - } - - private void loadDashboards(Path dashboardsDir, TenantId tenantId, CustomerId customerId) throws Exception { - try (DirectoryStream dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { - dirStream.forEach( - path -> { - try { - JsonNode dashboardJson = objectMapper.readTree(path.toFile()); - Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class); - dashboard.setTenantId(tenantId); - Dashboard savedDashboard = dashboardService.saveDashboard(dashboard); - if (customerId != null && !customerId.isNullUid()) { - dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId); - } - } catch (Exception e) { - log.error("Unable to load dashboard from json: [{}]", path.toString()); - throw new RuntimeException("Unable to load dashboard from json", e); - } - } - ); - } - } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java new file mode 100644 index 0000000000..de43966448 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -0,0 +1,185 @@ +/** + * 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.service.install; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; + +/** + * Created by ashvayka on 18.04.18. + */ +@Component +@Slf4j +public class InstallScripts { + + public static final String APP_DIR = "application"; + public static final String SRC_DIR = "src"; + public static final String MAIN_DIR = "main"; + public static final String DATA_DIR = "data"; + public static final String JSON_DIR = "json"; + public static final String SYSTEM_DIR = "system"; + public static final String TENANT_DIR = "tenant"; + public static final String DEMO_DIR = "demo"; + public static final String RULE_CHAINS_DIR = "rule_chains"; + public static final String WIDGET_BUNDLES_DIR = "widget_bundles"; + public static final String DASHBOARDS_DIR = "dashboards"; + + public static final String JSON_EXT = ".json"; + + @Value("${install.data_dir:}") + private String dataDir; + + @Autowired + private RuleChainService ruleChainService; + + @Autowired + private DashboardService dashboardService; + + @Autowired + private WidgetTypeService widgetTypeService; + + @Autowired + private WidgetsBundleService widgetsBundleService; + + public Path getTenantRuleChainsDir() { + return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); + } + + public String getDataDir() { + if (!StringUtils.isEmpty(dataDir)) { + if (!Paths.get(this.dataDir).toFile().isDirectory()) { + throw new RuntimeException("'install.data_dir' property value is not a valid directory!"); + } + return dataDir; + } else { + String workDir = System.getProperty("user.dir"); + if (workDir.endsWith("application")) { + return Paths.get(workDir, SRC_DIR, MAIN_DIR, DATA_DIR).toString(); + } else { + Path dataDirPath = Paths.get(workDir, APP_DIR, SRC_DIR, MAIN_DIR, DATA_DIR); + if (Files.exists(dataDirPath)) { + return dataDirPath.toString(); + } else { + throw new RuntimeException("Not valid working directory: " + workDir + ". Please use either root project directory, application module directory or specify valid \"install.data_dir\" ENV variable to avoid automatic data directory lookup!"); + } + } + } + } + + public void createDefaultRuleChains(TenantId tenantId) throws IOException { + Path tenantChainsDir = getTenantRuleChainsDir(); + try (DirectoryStream dirStream = Files.newDirectoryStream(tenantChainsDir, path -> path.toString().endsWith(InstallScripts.JSON_EXT))) { + dirStream.forEach( + path -> { + try { + JsonNode ruleChainJson = objectMapper.readTree(path.toFile()); + RuleChain ruleChain = objectMapper.treeToValue(ruleChainJson.get("ruleChain"), RuleChain.class); + RuleChainMetaData ruleChainMetaData = objectMapper.treeToValue(ruleChainJson.get("metadata"), RuleChainMetaData.class); + + ruleChain.setTenantId(tenantId); + ruleChain = ruleChainService.saveRuleChain(ruleChain); + + ruleChainMetaData.setRuleChainId(ruleChain.getId()); + ruleChainService.saveRuleChainMetaData(ruleChainMetaData); + } catch (Exception e) { + log.error("Unable to load rule chain from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load rule chain from json", e); + } + } + ); + } + } + + public void loadSystemWidgets() throws Exception { + Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR); + try (DirectoryStream dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) { + dirStream.forEach( + path -> { + try { + JsonNode widgetsBundleDescriptorJson = objectMapper.readTree(path.toFile()); + JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle"); + WidgetsBundle widgetsBundle = objectMapper.treeToValue(widgetsBundleJson, WidgetsBundle.class); + WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); + JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes"); + widgetTypesArrayJson.forEach( + widgetTypeJson -> { + try { + WidgetType widgetType = objectMapper.treeToValue(widgetTypeJson, WidgetType.class); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + widgetTypeService.saveWidgetType(widgetType); + } catch (Exception e) { + log.error("Unable to load widget type from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load widget type from json", e); + } + } + ); + } catch (Exception e) { + log.error("Unable to load widgets bundle from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load widgets bundle from json", e); + } + } + ); + } + } + + public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception { + Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR); + try (DirectoryStream dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { + dirStream.forEach( + path -> { + try { + JsonNode dashboardJson = objectMapper.readTree(path.toFile()); + Dashboard dashboard = objectMapper.treeToValue(dashboardJson, Dashboard.class); + dashboard.setTenantId(tenantId); + Dashboard savedDashboard = dashboardService.saveDashboard(dashboard); + if (customerId != null && !customerId.isNullUid()) { + dashboardService.assignDashboardToCustomer(savedDashboard.getId(), customerId); + } + } catch (Exception e) { + log.error("Unable to load dashboard from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load dashboard from json", e); + } + } + ); + } + } + + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java index 443ec0c5e0..535e0f96da 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseSchemaService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; @@ -38,9 +39,6 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService { private static final String SQL_DIR = "sql"; private static final String SCHEMA_SQL = "schema.sql"; - @Value("${install.data_dir}") - private String dataDir; - @Value("${spring.datasource.url}") private String dbUrl; @@ -50,12 +48,15 @@ public class SqlDatabaseSchemaService implements DatabaseSchemaService { @Value("${spring.datasource.password}") private String dbPassword; + @Autowired + private InstallScripts installScripts; + @Override public void createDatabaseSchema() throws Exception { log.info("Installing SQL DataBase schema..."); - Path schemaFile = Paths.get(this.dataDir, SQL_DIR, SCHEMA_SQL); + Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, SCHEMA_SQL); try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8")); conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 412ea8b9e6..39db831cff 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -44,9 +44,6 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { private static final String SCHEMA_UPDATE_SQL = "schema_update.sql"; - @Value("${install.data_dir}") - private String dataDir; - @Value("${spring.datasource.url}") private String dbUrl; @@ -59,12 +56,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { @Autowired private DashboardService dashboardService; + @Autowired + private InstallScripts installScripts; + @Override public void upgradeDatabase(String fromVersion) throws Exception { switch (fromVersion) { case "1.3.0": log.info("Updating schema ..."); - Path schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.3.1", SCHEMA_UPDATE_SQL); + Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.3.1", SCHEMA_UPDATE_SQL); try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script @@ -82,7 +82,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { log.info("Dashboards dumped."); log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.4.0", SCHEMA_UPDATE_SQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.4.0", SCHEMA_UPDATE_SQL); String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script log.info("Schema updated."); @@ -100,7 +100,7 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService { case "1.4.0": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); - schemaUpdateFile = Paths.get(this.dataDir, "upgrade", "1.5.0", SCHEMA_UPDATE_SQL); + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "1.5.0", SCHEMA_UPDATE_SQL); String sql = new String(Files.readAllBytes(schemaUpdateFile), Charset.forName("UTF-8")); conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script log.info("Schema updated."); diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index a3dcb68573..f3a6af4880 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -23,10 +23,6 @@ public interface SystemDataLoaderService { void loadSystemWidgets() throws Exception; - void loadSystemPlugins() throws Exception; - - void loadSystemRules() throws Exception; - void loadDemoData() throws Exception; void deleteSystemWidgetBundle(String bundleAlias) throws Exception; diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java new file mode 100644 index 0000000000..9674490334 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java @@ -0,0 +1,157 @@ +/** + * 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.service.rpc; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.context.request.async.DeferredResult; +import org.thingsboard.server.actors.service.ActorService; +import org.thingsboard.server.common.data.audit.ActionType; +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.data.id.UUIDBased; +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.controller.BaseController; +import org.thingsboard.server.dao.audit.AuditLogService; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.RpcError; +import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; +import org.thingsboard.server.service.security.model.SecurityUser; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +/** + * Created by ashvayka on 27.03.18. + */ +@Service +@Slf4j +public class DefaultDeviceRpcService implements DeviceRpcService { + + @Autowired + private ClusterRoutingService routingService; + + @Autowired + private ClusterRpcService rpcService; + + @Autowired + private ActorService actorService; + + @Autowired + private AuditLogService auditLogService; + + private ScheduledExecutorService rpcCallBackExecutor; + + private final ConcurrentMap> localRpcRequests = new ConcurrentHashMap<>(); + + + @PostConstruct + public void initExecutor() { + rpcCallBackExecutor = Executors.newSingleThreadScheduledExecutor(); + } + + @PreDestroy + public void shutdownExecutor() { + if (rpcCallBackExecutor != null) { + rpcCallBackExecutor.shutdownNow(); + } + } + + @Override + public void process(ToDeviceRpcRequest request, Consumer responseConsumer) { + log.trace("[{}] Processing local rpc call for device [{}]", request.getTenantId(), request.getDeviceId()); + sendRpcRequest(request); + UUID requestId = request.getId(); + localRpcRequests.put(requestId, responseConsumer); + long timeout = Math.max(0, request.getExpirationTime() - System.currentTimeMillis()); + log.error("[{}] processing the request: [{}]", this.hashCode(), requestId); + rpcCallBackExecutor.schedule(() -> { + log.error("[{}] timeout the request: [{}]", this.hashCode(), requestId); + Consumer consumer = localRpcRequests.remove(requestId); + if (consumer != null) { + consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT)); + } + }, timeout, TimeUnit.MILLISECONDS); + } + + @Override + public void process(ToDeviceRpcRequest request, ServerAddress originator) { +// if (pluginServerAddress.isPresent()) { +// systemContext.getRpcService().tell(pluginServerAddress.get(), responsePluginMsg); +// logger.debug("[{}] Rpc command response sent to remote plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); +// } else { +// context.parent().tell(responsePluginMsg, ActorRef.noSender()); +// logger.debug("[{}] Rpc command response sent to local plugin actor [{}]!", deviceId, requestMd.getMsg().getMsg().getId()); +// } + } + + @Override + public void process(FromDeviceRpcResponse response) { + log.error("[{}] response the request: [{}]", this.hashCode(), response.getId()); + //TODO: send to another server if needed. + UUID requestId = response.getId(); + Consumer consumer = localRpcRequests.remove(requestId); + if (consumer != null) { + consumer.accept(response); + } else { + log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); + } + } + + @Override + public void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) { + ToServerRpcResponseActorMsg rpcMsg = new ToServerRpcResponseActorMsg(tenantId, deviceId, new ToServerRpcResponseMsg(requestId, body)); + forward(deviceId, rpcMsg, rpcService::tell); + } + + private void sendRpcRequest(ToDeviceRpcRequest msg) { + log.trace("[{}] Forwarding msg {} to device actor!", msg.getDeviceId(), msg); + ToDeviceRpcRequestActorMsg rpcMsg = new ToDeviceRpcRequestActorMsg(msg); + forward(msg.getDeviceId(), rpcMsg, rpcService::tell); + } + + private void forward(DeviceId deviceId, T msg, BiConsumer rpcFunction) { + Optional instance = routingService.resolveById(deviceId); + if (instance.isPresent()) { + log.trace("[{}] Forwarding msg {} to remote device actor!", msg.getTenantId(), msg); + rpcFunction.accept(instance.get(), msg); + } else { + log.trace("[{}] Forwarding msg {} to local device actor!", msg.getTenantId(), msg); + actorService.onMsg(msg); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java new file mode 100644 index 0000000000..429912000c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java @@ -0,0 +1,39 @@ +/** + * 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.service.rpc; + +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; + +import java.util.function.Consumer; + +/** + * Created by ashvayka on 16.04.18. + */ +public interface DeviceRpcService { + + void process(ToDeviceRpcRequest request, Consumer responseConsumer); + + void process(ToDeviceRpcRequest request, ServerAddress originator); + + void process(FromDeviceRpcResponse response); + + void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body); + +} diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/LocalRequestMetaData.java b/application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java similarity index 78% rename from extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/LocalRequestMetaData.java rename to application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java index 67dff86b3d..1416c97963 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/LocalRequestMetaData.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/LocalRequestMetaData.java @@ -13,18 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.core.plugin.rpc; +package org.thingsboard.server.service.rpc; import lombok.Data; import org.springframework.http.ResponseEntity; import org.springframework.web.context.request.async.DeferredResult; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.service.security.model.SecurityUser; /** - * @author Andrew Shvayka + * Created by ashvayka on 16.04.18. */ @Data public class LocalRequestMetaData { private final ToDeviceRpcRequest request; + private final SecurityUser user; private final DeferredResult responseWriter; } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java similarity index 74% rename from extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java rename to application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java index faf169655d..ae7cab5b2b 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestPluginMsg.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToDeviceRpcRequestActorMsg.java @@ -13,36 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.api.plugins.msg; +package org.thingsboard.server.service.rpc; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.PluginId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; import java.util.Optional; /** - * @author Andrew Shvayka + * Created by ashvayka on 16.04.18. */ @ToString @RequiredArgsConstructor -public class ToDeviceRpcRequestPluginMsg implements ToDeviceActorNotificationMsg { +public class ToDeviceRpcRequestActorMsg implements ToDeviceActorNotificationMsg { private final ServerAddress serverAddress; @Getter - private final PluginId pluginId; - @Getter - private final TenantId pluginTenantId; - @Getter private final ToDeviceRpcRequest msg; - public ToDeviceRpcRequestPluginMsg(PluginId pluginId, TenantId pluginTenantId, ToDeviceRpcRequest msg) { - this(null, pluginId, pluginTenantId, msg); + public ToDeviceRpcRequestActorMsg(ToDeviceRpcRequest msg) { + this(null, msg); } public Optional getServerAddress() { @@ -58,5 +55,9 @@ public class ToDeviceRpcRequestPluginMsg implements ToDeviceActorNotificationMsg public TenantId getTenantId() { return msg.getTenantId(); } -} + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java b/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java new file mode 100644 index 0000000000..f3183ec846 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/rpc/ToServerRpcResponseActorMsg.java @@ -0,0 +1,60 @@ +/** + * 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.service.rpc; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; +import org.thingsboard.server.extensions.api.device.ToDeviceActorNotificationMsg; + +import java.util.Optional; + +/** + * Created by ashvayka on 16.04.18. + */ +@ToString +@RequiredArgsConstructor +public class ToServerRpcResponseActorMsg implements ToDeviceActorNotificationMsg { + + private final ServerAddress serverAddress; + + @Getter + private final TenantId tenantId; + + @Getter + private final DeviceId deviceId; + + @Getter + private final ToServerRpcResponseMsg msg; + + public ToServerRpcResponseActorMsg(TenantId tenantId, DeviceId deviceId, ToServerRpcResponseMsg msg) { + this(null, tenantId, deviceId, msg); + } + + public Optional getServerAddress() { + return Optional.ofNullable(serverAddress); + } + + @Override + public MsgType getMsgType() { + return MsgType.SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java index d68f6fed29..8e689ec32d 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsEngine.java @@ -118,7 +118,7 @@ public class NashornJsEngine implements org.thingsboard.rule.engine.api.ScriptEn String newData = data != null ? data : msg.getData(); TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData(); String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); - return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData); + return new TbMsg(msg.getId(), newMessageType, msg.getOriginator(), newMetadata, newData, msg.getRuleChainId(), msg.getRuleNodeId(), msg.getClusterPartition()); } catch (Throwable th) { th.printStackTrace(); throw new RuntimeException("Failed to unbind message data from javascript result", th); diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index c1f3688e92..8487279ca6 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -25,6 +25,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Component; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.actors.plugin.ValidationResult; +import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Tenant; @@ -35,8 +36,10 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; 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.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.controller.HttpValidationCallback; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; @@ -140,7 +143,7 @@ public class AccessValidator { return response; } - public void validate(SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + public void validate(SecurityUser currentUser, EntityId entityId, FutureCallback callback) { switch (entityId.getEntityType()) { case DEVICE: validateDevice(currentUser, entityId, callback); @@ -177,14 +180,14 @@ public class AccessValidator { } else if (currentUser.isCustomerUser() && !device.getCustomerId().equals(currentUser.getCustomerId())) { return ValidationResult.accessDenied("Device doesn't belong to the current Customer!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(device); } } }), executor); } } - private void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateAsset(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -198,15 +201,14 @@ public class AccessValidator { } else if (currentUser.isCustomerUser() && !asset.getCustomerId().equals(currentUser.getCustomerId())) { return ValidationResult.accessDenied("Asset doesn't belong to the current Customer!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(asset); } } }), executor); } } - - private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateRuleChain(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { if (currentUser.isCustomerUser()) { callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -220,14 +222,40 @@ public class AccessValidator { } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) { return ValidationResult.accessDenied("Rule chain is not in system scope!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(ruleChain); + } + } + }), executor); + } + } + + private void validateRule(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + if (currentUser.isCustomerUser()) { + callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); + } else { + ListenableFuture ruleNodeFuture = ruleChainService.findRuleNodeByIdAsync(new RuleNodeId(entityId.getId())); + Futures.addCallback(ruleNodeFuture, getCallback(callback, ruleNodeTmp -> { + RuleNode ruleNode = ruleNodeTmp; + if (ruleNode == null) { + return ValidationResult.entityNotFound("Rule node with requested id wasn't found!"); + } else if (ruleNode.getRuleChainId() == null) { + return ValidationResult.entityNotFound("Rule chain with requested node id wasn't found!"); + } else { + //TODO: make async + RuleChain ruleChain = ruleChainService.findRuleChainById(ruleNode.getRuleChainId()); + if (currentUser.isTenantAdmin() && !ruleChain.getTenantId().equals(currentUser.getTenantId())) { + return ValidationResult.accessDenied("Rule chain doesn't belong to the current Tenant!"); + } else if (currentUser.isSystemAdmin() && !ruleChain.getTenantId().isNullUid()) { + return ValidationResult.accessDenied("Rule chain is not in system scope!"); + } else { + return ValidationResult.ok(ruleNode); } } }), executor); } } - private void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateCustomer(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { @@ -241,18 +269,18 @@ public class AccessValidator { } else if (currentUser.isCustomerUser() && !customer.getId().equals(currentUser.getCustomerId())) { return ValidationResult.accessDenied("Customer doesn't relate to the currently authorized customer user!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(customer); } } }), executor); } } - private void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { + private void validateTenant(final SecurityUser currentUser, EntityId entityId, FutureCallback callback) { if (currentUser.isCustomerUser()) { callback.onSuccess(ValidationResult.accessDenied(CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else if (currentUser.isSystemAdmin()) { - callback.onSuccess(ValidationResult.ok()); + callback.onSuccess(ValidationResult.ok(null)); } else { ListenableFuture tenantFuture = tenantService.findTenantByIdAsync(new TenantId(entityId.getId())); Futures.addCallback(tenantFuture, getCallback(callback, tenant -> { @@ -261,13 +289,13 @@ public class AccessValidator { } else if (!tenant.getId().equals(currentUser.getTenantId())) { return ValidationResult.accessDenied("Tenant doesn't relate to the currently authorized user!"); } else { - return ValidationResult.ok(); + return ValidationResult.ok(tenant); } }), executor); } } - private FutureCallback getCallback(FutureCallback callback, Function transformer) { + private FutureCallback getCallback(FutureCallback callback, Function> transformer) { return new FutureCallback() { @Override public void onSuccess(@Nullable T result) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java new file mode 100644 index 0000000000..fc17ccd416 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -0,0 +1,390 @@ +/** + * 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.service.state; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.ObjectMapper; +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 com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.thingsboard.server.actors.service.ActorService; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; + +import javax.annotation.Nullable; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.thingsboard.server.common.data.DataConstants.ACTIVITY_EVENT; +import static org.thingsboard.server.common.data.DataConstants.CONNECT_EVENT; +import static org.thingsboard.server.common.data.DataConstants.DISCONNECT_EVENT; +import static org.thingsboard.server.common.data.DataConstants.INACTIVITY_EVENT; + +/** + * Created by ashvayka on 01.05.18. + */ +@Service +@Slf4j +//TODO: refactor to use page links as cursor and not fetch all +public class DefaultDeviceStateService implements DeviceStateService { + + private static final ObjectMapper json = new ObjectMapper(); + public static final String ACTIVITY_STATE = "active"; + public static final String LAST_CONNECT_TIME = "lastConnectTime"; + public static final String LAST_DISCONNECT_TIME = "lastDisconnectTime"; + public static final String LAST_ACTIVITY_TIME = "lastActivityTime"; + public static final String INACTIVITY_ALARM_TIME = "inactivityAlarmTime"; + public static final String INACTIVITY_TIMEOUT = "inactivityTimeout"; + + public static final List PERSISTENT_ATTRIBUTES = Arrays.asList(ACTIVITY_STATE, LAST_CONNECT_TIME, LAST_DISCONNECT_TIME, LAST_ACTIVITY_TIME, INACTIVITY_ALARM_TIME, INACTIVITY_TIMEOUT); + + @Autowired + private TenantService tenantService; + + @Autowired + private DeviceService deviceService; + + @Autowired + private AttributesService attributesService; + + @Autowired + private ActorService actorService; + + @Autowired + private TelemetrySubscriptionService tsSubService; + + @Value("${state.defaultInactivityTimeoutInSec}") + @Getter + private long defaultInactivityTimeoutInSec; + + @Value("${state.defaultStateCheckIntervalInSec}") + @Getter + private long defaultStateCheckIntervalInSec; + +// TODO in v2.1 +// @Value("${state.defaultStatePersistenceIntervalInSec}") +// @Getter +// private long defaultStatePersistenceIntervalInSec; +// +// @Value("${state.defaultStatePersistencePack}") +// @Getter +// private long defaultStatePersistencePack; + + private ListeningScheduledExecutorService queueExecutor; + + private ConcurrentMap> tenantDevices = new ConcurrentHashMap<>(); + private ConcurrentMap deviceStates = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + // Should be always single threaded due to absence of locks. + queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor()); + queueExecutor.submit(this::initStateFromDB); + queueExecutor.scheduleAtFixedRate(this::updateState, defaultStateCheckIntervalInSec, defaultStateCheckIntervalInSec, TimeUnit.SECONDS); + //TODO: schedule persistence in v2.1; + } + + @PreDestroy + public void stop() { + if (queueExecutor != null) { + queueExecutor.shutdownNow(); + } + } + + @Override + public void onDeviceAdded(Device device) { + queueExecutor.submit(() -> onDeviceAddedSync(device)); + } + + @Override + public void onDeviceUpdated(Device device) { + queueExecutor.submit(() -> onDeviceUpdatedSync(device)); + } + + @Override + public void onDeviceConnect(DeviceId deviceId) { + queueExecutor.submit(() -> onDeviceConnectSync(deviceId)); + } + + @Override + public void onDeviceActivity(DeviceId deviceId) { + queueExecutor.submit(() -> onDeviceActivitySync(deviceId)); + } + + @Override + public void onDeviceDisconnect(DeviceId deviceId) { + queueExecutor.submit(() -> onDeviceDisconnectSync(deviceId)); + } + + @Override + public void onDeviceDeleted(Device device) { + queueExecutor.submit(() -> onDeviceDeleted(device.getTenantId(), device.getId())); + } + + @Override + public void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) { + queueExecutor.submit(() -> onInactivityTimeoutUpdate(deviceId, inactivityTimeout)); + } + + @Override + public Optional getDeviceState(DeviceId deviceId) { + DeviceStateData state = deviceStates.get(deviceId); + if (state != null) { + return Optional.of(state.getState()); + } else { + return Optional.empty(); + } + } + + private void initStateFromDB() { + List tenants = tenantService.findTenants(new TextPageLink(Integer.MAX_VALUE)).getData(); + for (Tenant tenant : tenants) { + List> fetchFutures = new ArrayList<>(); + List devices = deviceService.findDevicesByTenantId(tenant.getId(), new TextPageLink(Integer.MAX_VALUE)).getData(); + for (Device device : devices) { + fetchFutures.add(fetchDeviceState(device)); + } + try { + Futures.successfulAsList(fetchFutures).get().forEach(this::addDeviceUsingState); + } catch (InterruptedException | ExecutionException e) { + log.warn("Failed to init device state service from DB", e); + } + } + } + + private void addDeviceUsingState(DeviceStateData state) { + tenantDevices.computeIfAbsent(state.getTenantId(), id -> ConcurrentHashMap.newKeySet()).add(state.getDeviceId()); + deviceStates.put(state.getDeviceId(), state); + } + + private void updateState() { + long ts = System.currentTimeMillis(); + Set deviceIds = new HashSet<>(deviceStates.keySet()); + for (DeviceId deviceId : deviceIds) { + DeviceStateData stateData = deviceStates.get(deviceId); + DeviceState state = stateData.getState(); + state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); + if (!state.isActive() && state.getLastInactivityAlarmTime() < state.getLastActivityTime()) { + state.setLastInactivityAlarmTime(ts); + pushRuleEngineMessage(stateData, INACTIVITY_EVENT); + saveAttribute(deviceId, INACTIVITY_ALARM_TIME, ts); + saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); + } + } + } + + private void onDeviceConnectSync(DeviceId deviceId) { + DeviceStateData stateData = deviceStates.get(deviceId); + if (stateData != null) { + long ts = System.currentTimeMillis(); + stateData.getState().setLastConnectTime(ts); + pushRuleEngineMessage(stateData, CONNECT_EVENT); + saveAttribute(deviceId, LAST_CONNECT_TIME, ts); + } + } + + private void onDeviceDisconnectSync(DeviceId deviceId) { + DeviceStateData stateData = deviceStates.get(deviceId); + if (stateData != null) { + long ts = System.currentTimeMillis(); + stateData.getState().setLastDisconnectTime(ts); + pushRuleEngineMessage(stateData, DISCONNECT_EVENT); + saveAttribute(deviceId, LAST_DISCONNECT_TIME, ts); + } + } + + private void onDeviceActivitySync(DeviceId deviceId) { + DeviceStateData stateData = deviceStates.get(deviceId); + if (stateData != null) { + DeviceState state = stateData.getState(); + long ts = System.currentTimeMillis(); + state.setActive(true); + stateData.getState().setLastActivityTime(ts); + pushRuleEngineMessage(stateData, ACTIVITY_EVENT); + saveAttribute(deviceId, LAST_ACTIVITY_TIME, ts); + saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); + } + } + + private void onInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout) { + if (inactivityTimeout == 0L) { + return; + } + DeviceStateData stateData = deviceStates.get(deviceId); + if (stateData != null) { + long ts = System.currentTimeMillis(); + DeviceState state = stateData.getState(); + state.setInactivityTimeout(inactivityTimeout); + boolean oldActive = state.isActive(); + state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); + if (!oldActive && state.isActive()) { + saveAttribute(deviceId, ACTIVITY_STATE, state.isActive()); + } + } + } + + private void onDeviceAddedSync(Device device) { + Futures.addCallback(fetchDeviceState(device), new FutureCallback() { + @Override + public void onSuccess(@Nullable DeviceStateData state) { + addDeviceUsingState(state); + } + + @Override + public void onFailure(Throwable t) { + log.warn("Failed to register device to the state service", t); + } + }); + } + + private void onDeviceUpdatedSync(Device device) { + DeviceStateData stateData = deviceStates.get(device.getId()); + if (stateData != null) { + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("deviceName", device.getName()); + md.putValue("deviceType", device.getType()); + stateData.setMetaData(md); + } + } + + private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) { + deviceStates.remove(deviceId); + Set deviceIds = tenantDevices.get(tenantId); + if (deviceIds != null) { + deviceIds.remove(deviceId); + if (deviceIds.isEmpty()) { + tenantDevices.remove(tenantId); + } + } + } + + private ListenableFuture fetchDeviceState(Device device) { + ListenableFuture> attributes = attributesService.find(device.getId(), DataConstants.SERVER_SCOPE, PERSISTENT_ATTRIBUTES); + return Futures.transform(attributes, new Function, DeviceStateData>() { + @Nullable + @Override + public DeviceStateData apply(@Nullable List attributes) { + long lastActivityTime = getAttributeValue(attributes, LAST_ACTIVITY_TIME, 0L); + long inactivityAlarmTime = getAttributeValue(attributes, INACTIVITY_ALARM_TIME, 0L); + long inactivityTimeout = getAttributeValue(attributes, INACTIVITY_TIMEOUT, TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)); + boolean active = System.currentTimeMillis() < lastActivityTime + inactivityTimeout; + DeviceState deviceState = DeviceState.builder() + .active(active) + .lastConnectTime(getAttributeValue(attributes, LAST_CONNECT_TIME, 0L)) + .lastDisconnectTime(getAttributeValue(attributes, LAST_DISCONNECT_TIME, 0L)) + .lastActivityTime(lastActivityTime) + .lastInactivityAlarmTime(inactivityAlarmTime) + .inactivityTimeout(inactivityTimeout) + .build(); + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("deviceName", device.getName()); + md.putValue("deviceType", device.getType()); + return DeviceStateData.builder() + .tenantId(device.getTenantId()) + .deviceId(device.getId()) + .metaData(md) + .state(deviceState).build(); + } + }); + } + + private long getLastPersistTime(List attributes) { + return attributes.stream().map(AttributeKvEntry::getLastUpdateTs).max(Long::compare).orElse(0L); + } + + private long getAttributeValue(List attributes, String attributeName, long defaultValue) { + for (AttributeKvEntry attribute : attributes) { + if (attribute.getKey().equals(attributeName)) { + return attribute.getLongValue().orElse(defaultValue); + } + } + return defaultValue; + } + + private void pushRuleEngineMessage(DeviceStateData stateData, String msgType) { + DeviceState state = stateData.getState(); + try { + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData(), TbMsgDataType.JSON + , json.writeValueAsString(state) + , null, null, 0L); + actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg)); + } catch (Exception e) { + log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e); + } + } + + private void saveAttribute(DeviceId deviceId, String key, long value) { + tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + } + + private void saveAttribute(DeviceId deviceId, String key, boolean value) { + tsSubService.saveAttrAndNotify(deviceId, DataConstants.SERVER_SCOPE, key, value, new AttributeSaveCallback(deviceId, key, value)); + } + + private class AttributeSaveCallback implements FutureCallback { + private final DeviceId deviceId; + private final String key; + private final Object value; + + AttributeSaveCallback(DeviceId deviceId, String key, Object value) { + this.deviceId = deviceId; + this.key = key; + this.value = value; + } + + @Override + public void onSuccess(@Nullable Void result) { + log.trace("[{}] Successfully updated attribute [{}] with value [{}]", deviceId, key, value); + } + + @Override + public void onFailure(Throwable t) { + log.warn("[{}] Failed to update attribute [{}] with value [{}]", deviceId, key, value, t); + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceState.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceState.java new file mode 100644 index 0000000000..29167ee519 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceState.java @@ -0,0 +1,35 @@ +/** + * 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.service.state; + +import lombok.Builder; +import lombok.Data; + +/** + * Created by ashvayka on 01.05.18. + */ +@Data +@Builder +public class DeviceState { + + private boolean active; + private long lastConnectTime; + private long lastActivityTime; + private long lastDisconnectTime; + private long lastInactivityAlarmTime; + private long inactivityTimeout; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java new file mode 100644 index 0000000000..cb9eeeaea7 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java @@ -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.service.state; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +/** + * Created by ashvayka on 01.05.18. + */ +@Data +@Builder +class DeviceStateData { + + private final TenantId tenantId; + private final DeviceId deviceId; + + private TbMsgMetaData metaData; + private final DeviceState state; + +} diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java new file mode 100644 index 0000000000..37f785cf44 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java @@ -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. + */ +package org.thingsboard.server.service.state; + +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.DeviceId; + +import java.util.Optional; + +/** + * Created by ashvayka on 01.05.18. + */ +public interface DeviceStateService { + + void onDeviceAdded(Device device); + + void onDeviceUpdated(Device device); + + void onDeviceDeleted(Device device); + + void onDeviceConnect(DeviceId deviceId); + + void onDeviceActivity(DeviceId deviceId); + + void onDeviceDisconnect(DeviceId deviceId); + + void onDeviceInactivityTimeoutUpdate(DeviceId deviceId, long inactivityTimeout); + + Optional getDeviceState(DeviceId deviceId); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 58bbec550e..00a337a38b 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -20,11 +20,20 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.dao.attributes.AttributesService; @@ -34,11 +43,14 @@ import org.thingsboard.server.extensions.core.plugin.telemetry.sub.Subscription; import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionState; import org.thingsboard.server.extensions.core.plugin.telemetry.sub.SubscriptionUpdate; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; +import org.thingsboard.server.service.state.DefaultDeviceStateService; +import org.thingsboard.server.service.state.DeviceStateService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -70,6 +82,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio @Autowired private ClusterRoutingService routingService; + @Autowired + @Lazy + private DeviceStateService stateService; + private ExecutorService tsCallBackExecutor; private ExecutorService wsCallBackExecutor; @@ -149,10 +165,41 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio addWsCallback(saveFuture, success -> onAttributesUpdate(entityId, scope, attributes)); } + @Override + public void saveAttrAndNotify(EntityId entityId, String scope, String key, long value, FutureCallback callback) { + saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new LongDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + + @Override + public void saveAttrAndNotify(EntityId entityId, String scope, String key, String value, FutureCallback callback) { + saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + + @Override + public void saveAttrAndNotify(EntityId entityId, String scope, String key, double value, FutureCallback callback) { + saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new DoubleDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + + @Override + public void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback callback) { + saveAndNotify(entityId, scope, Collections.singletonList(new BaseAttributeKvEntry(new BooleanDataEntry(key, value) + , System.currentTimeMillis())), callback); + } + private void onAttributesUpdate(EntityId entityId, String scope, List attributes) { Optional serverAddress = routingService.resolveById(entityId); if (!serverAddress.isPresent()) { onLocalAttributesUpdate(entityId, scope, attributes); + if (entityId.getEntityType() == EntityType.DEVICE && DataConstants.SERVER_SCOPE.equalsIgnoreCase(scope)) { + for (AttributeKvEntry attribute : attributes) { + if (attribute.getKey().equals(DefaultDeviceStateService.INACTIVITY_TIMEOUT)) { + stateService.onDeviceInactivityTimeoutUpdate(new DeviceId(entityId.getId()), attribute.getLongValue().orElse(0L)); + } + } + } } else { // rpcHandler.onAttributesUpdate(ctx, serverAddress.get(), entityId, entries); } diff --git a/application/src/main/proto/cluster.proto b/application/src/main/proto/cluster.proto index ba72097343..fc924875c4 100644 --- a/application/src/main/proto/cluster.proto +++ b/application/src/main/proto/cluster.proto @@ -61,7 +61,6 @@ message ConnectRpcMessage { } message ToDeviceRpcRequestRpcMessage { - PluginAddress address = 1; Uid deviceTenantId = 2; Uid deviceId = 3; @@ -73,8 +72,6 @@ message ToDeviceRpcRequestRpcMessage { } message ToPluginRpcResponseRpcMessage { - PluginAddress address = 1; - Uid msgId = 2; string response = 3; string error = 4; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index a9e47de87e..af5b8d4de2 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -207,24 +207,15 @@ actors: sync: # Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}" - plugin: - # Default timeout for termination of the plugin actor after it is stopped - termination.delay: "${ACTORS_PLUGIN_TERMINATION_DELAY:60000}" - # Default timeout for processing of particular message by particular plugin - processing.timeout: "${ACTORS_PLUGIN_TIMEOUT:60000}" - # Errors for particular actor are persisted once per specified amount of milliseconds - error_persist_frequency: "${ACTORS_PLUGIN_ERROR_FREQUENCY:3000}" rule: - # Default timeout for termination of the rule actor after it is stopped - termination.delay: "${ACTORS_RULE_TERMINATION_DELAY:30000}" - # Errors for particular actor are persisted once per specified amount of milliseconds - error_persist_frequency: "${ACTORS_RULE_ERROR_FREQUENCY:3000}" # Specify thread pool size for database request callbacks executor service db_callback_thread_pool_size: "${ACTORS_RULE_DB_CALLBACK_THREAD_POOL_SIZE:1}" # Specify thread pool size for javascript executor service js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:10}" # Specify thread pool size for mail sender executor service mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:10}" + # Specify thread pool size for external call service + external_call_thread_pool_size: "${ACTORS_RULE_EXTERNAL_CALL_THREAD_POOL_SIZE:10}" chain: # Errors for particular actor are persisted once per specified amount of milliseconds error_persist_frequency: "${ACTORS_RULE_CHAIN_ERROR_FREQUENCY:3000}" @@ -235,6 +226,13 @@ actors: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}" + queue: + # Enable/disable persistence of un-processed messages to the queue + enabled: "${ACTORS_QUEUE_ENABLED:true}" + # Maximum allowed timeout for persistence into the queue + timeout: "${ACTORS_QUEUE_PERSISTENCE_TIMEOUT:30000}" + client_side_rpc: + timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" cache: # caffeine or redis @@ -306,7 +304,9 @@ spring: rule: queue: - msg_partitioning: "${QUEUE_MSG_PARTITIONING:HOURS}" + type: "memory" + max_size: 10000 + # PostgreSQL DAO Configuration #spring: @@ -359,4 +359,11 @@ audit_log: host: "${AUDIT_LOG_SINK_HOST:localhost}" port: "${AUDIT_LOG_SINK_POST:9200}" user_name: "${AUDIT_LOG_SINK_USER_NAME:}" - password: "${AUDIT_LOG_SINK_PASSWORD:}" \ No newline at end of file + password: "${AUDIT_LOG_SINK_PASSWORD:}" + +state: + defaultInactivityTimeoutInSec: 10 + defaultStateCheckIntervalInSec: 10 +# TODO in v2.1 +# defaultStatePersistenceIntervalInSec: 60 +# defaultStatePersistencePack: 100 \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java b/application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java deleted file mode 100644 index c2b5de967a..0000000000 --- a/application/src/test/java/org/thingsboard/server/actors/ActorsTestSuite.java +++ /dev/null @@ -1,27 +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; - -import org.junit.extensions.cpsuite.ClasspathSuite; -import org.junit.runner.RunWith; - -/** - * @author Andrew Shvayka - */ -@RunWith(ClasspathSuite.class) -@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.actors.*Test"}) -public class ActorsTestSuite { -} diff --git a/application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java b/application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java deleted file mode 100644 index d77341adee..0000000000 --- a/application/src/test/java/org/thingsboard/server/actors/DefaultActorServiceTest.java +++ /dev/null @@ -1,245 +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; - -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.*; - -import com.google.common.util.concurrent.Futures; -import org.thingsboard.server.actors.service.DefaultActorService; -import org.thingsboard.server.common.data.id.*; -import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.plugin.ComponentDescriptor; -import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; -import org.thingsboard.server.common.data.plugin.ComponentType; -import org.thingsboard.server.common.msg.session.*; -import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.event.EventService; -import org.thingsboard.server.gen.discovery.ServerInstanceProtos; -import org.thingsboard.server.service.cluster.discovery.DiscoveryService; -import org.thingsboard.server.service.cluster.discovery.ServerInstance; -import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; -import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; -import org.thingsboard.server.service.component.ComponentDiscoveryService; -import org.thingsboard.server.common.transport.auth.DeviceAuthResult; -import org.thingsboard.server.common.transport.auth.DeviceAuthService; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.plugin.PluginMetaData; -import org.thingsboard.server.common.data.rule.RuleMetaData; -import org.thingsboard.server.common.data.security.DeviceCredentialsFilter; -import org.thingsboard.server.common.data.security.DeviceTokenCredentials; -import org.thingsboard.server.common.msg.core.BasicTelemetryUploadRequest; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.plugin.PluginService; -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.core.plugin.telemetry.TelemetryStoragePlugin; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; -import org.springframework.test.util.ReflectionTestUtils; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -public class DefaultActorServiceTest { - - private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); - - private static final String PLUGIN_ID = "9fb2e951-e298-4acb-913a-db69af8a15f4"; - private static final String FILTERS_CONFIGURATION = - "[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", \"name\":\"TelemetryFilter\", \"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]"; - private static final String ACTION_CONFIGURATION = "{\"pluginToken\":\"telemetry\", \"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", \"configuration\":{}}"; - private static final String PLUGIN_CONFIGURATION = "{}"; - private DefaultActorService actorService; - private ActorSystemContext actorContext; - - private PluginService pluginService; - private RuleService ruleService; - private DeviceAuthService deviceAuthService; - private DeviceService deviceService; - private TimeseriesService tsService; - private TenantService tenantService; - private ClusterRpcService rpcService; - private DiscoveryService discoveryService; - private ClusterRoutingService routingService; - private AttributesService attributesService; - private ComponentDiscoveryService componentService; - private EventService eventService; - private ServerInstance serverInstance; - - private RuleMetaData ruleMock; - private PluginMetaData pluginMock; - private RuleId ruleId = new RuleId(UUID.randomUUID()); - private PluginId pluginId = new PluginId(UUID.fromString(PLUGIN_ID)); - private TenantId tenantId = new TenantId(UUID.randomUUID()); - - - @Before - public void before() throws Exception { - actorService = new DefaultActorService(); - actorContext = new ActorSystemContext(); - - tenantService = mock(TenantService.class); - pluginService = mock(PluginService.class); - ruleService = mock(RuleService.class); - deviceAuthService = mock(DeviceAuthService.class); - deviceService = mock(DeviceService.class); - tsService = mock(TimeseriesService.class); - rpcService = mock(ClusterRpcService.class); - discoveryService = mock(DiscoveryService.class); - routingService = mock(ClusterRoutingService.class); - attributesService = mock(AttributesService.class); - componentService = mock(ComponentDiscoveryService.class); - eventService = mock(EventService.class); - serverInstance = new ServerInstance(ServerInstanceProtos.ServerInfo.newBuilder().setHost("localhost").setPort(8080).build()); - - ReflectionTestUtils.setField(actorService, "actorContext", actorContext); - ReflectionTestUtils.setField(actorService, "rpcService", rpcService); - ReflectionTestUtils.setField(actorService, "discoveryService", discoveryService); - - ReflectionTestUtils.setField(actorContext, "syncSessionTimeout", 10000L); - ReflectionTestUtils.setField(actorContext, "pluginActorTerminationDelay", 10000L); - ReflectionTestUtils.setField(actorContext, "pluginErrorPersistFrequency", 10000L); - ReflectionTestUtils.setField(actorContext, "ruleActorTerminationDelay", 10000L); - ReflectionTestUtils.setField(actorContext, "ruleErrorPersistFrequency", 10000L); - ReflectionTestUtils.setField(actorContext, "pluginProcessingTimeout", 60000L); - ReflectionTestUtils.setField(actorContext, "tenantService", tenantService); - ReflectionTestUtils.setField(actorContext, "pluginService", pluginService); - ReflectionTestUtils.setField(actorContext, "ruleService", ruleService); - ReflectionTestUtils.setField(actorContext, "deviceAuthService", deviceAuthService); - ReflectionTestUtils.setField(actorContext, "deviceService", deviceService); - ReflectionTestUtils.setField(actorContext, "tsService", tsService); - ReflectionTestUtils.setField(actorContext, "rpcService", rpcService); - ReflectionTestUtils.setField(actorContext, "discoveryService", discoveryService); - ReflectionTestUtils.setField(actorContext, "tsService", tsService); - ReflectionTestUtils.setField(actorContext, "routingService", routingService); - ReflectionTestUtils.setField(actorContext, "attributesService", attributesService); - ReflectionTestUtils.setField(actorContext, "componentService", componentService); - ReflectionTestUtils.setField(actorContext, "eventService", eventService); - - - when(routingService.resolveById((EntityId) any())).thenReturn(Optional.empty()); - - when(discoveryService.getCurrentServer()).thenReturn(serverInstance); - - ruleMock = mock(RuleMetaData.class); - when(ruleMock.getId()).thenReturn(ruleId); - when(ruleMock.getState()).thenReturn(ComponentLifecycleState.ACTIVE); - when(ruleMock.getPluginToken()).thenReturn("telemetry"); - TextPageData systemRules = new TextPageData<>(Collections.emptyList(), null, false); - TextPageData tenantRules = new TextPageData<>(Collections.singletonList(ruleMock), null, false); - when(ruleService.findSystemRules(any())).thenReturn(systemRules); - when(ruleService.findTenantRules(any(), any())).thenReturn(tenantRules); - when(ruleService.findRuleById(ruleId)).thenReturn(ruleMock); - - pluginMock = mock(PluginMetaData.class); - when(pluginMock.getTenantId()).thenReturn(SYSTEM_TENANT); - when(pluginMock.getId()).thenReturn(pluginId); - when(pluginMock.getState()).thenReturn(ComponentLifecycleState.ACTIVE); - TextPageData systemPlugins = new TextPageData<>(Collections.singletonList(pluginMock), null, false); - TextPageData tenantPlugins = new TextPageData<>(Collections.emptyList(), null, false); - when(pluginService.findSystemPlugins(any())).thenReturn(systemPlugins); - when(pluginService.findTenantPlugins(any(), any())).thenReturn(tenantPlugins); - when(pluginService.findPluginByApiToken("telemetry")).thenReturn(pluginMock); - when(pluginService.findPluginById(pluginId)).thenReturn(pluginMock); - - TextPageData tenants = new TextPageData<>(Collections.emptyList(), null, false); - when(tenantService.findTenants(any())).thenReturn(tenants); - } - - private void initActorSystem() { - actorService.initActorSystem(); - } - - @After - public void after() { - actorService.stopActorSystem(); - } - - @Test - public void testBasicPostWithSyncSession() throws Exception { - SessionContext ssnCtx = mock(SessionContext.class); - KvEntry entry1 = new StringDataEntry("key1", "value1"); - KvEntry entry2 = new StringDataEntry("key2", "value2"); - BasicTelemetryUploadRequest telemetry = new BasicTelemetryUploadRequest(); - long ts = 42; - telemetry.add(ts, entry1); - telemetry.add(ts, entry2); - BasicAdaptorToSessionActorMsg msg = new BasicAdaptorToSessionActorMsg(ssnCtx, telemetry); - - DeviceId deviceId = new DeviceId(UUID.randomUUID()); - - DeviceCredentialsFilter filter = new DeviceTokenCredentials("token1"); - Device device = mock(Device.class); - - when(device.getId()).thenReturn(deviceId); - when(device.getTenantId()).thenReturn(tenantId); - when(ssnCtx.getSessionId()).thenReturn(new DummySessionID("session1")); - when(ssnCtx.getSessionType()).thenReturn(SessionType.SYNC); - when(deviceAuthService.process(filter)).thenReturn(DeviceAuthResult.of(deviceId)); - when(deviceService.findDeviceById(deviceId)).thenReturn(device); - - ObjectMapper ruleMapper = new ObjectMapper(); - when(ruleMock.getFilters()).thenReturn(ruleMapper.readTree(FILTERS_CONFIGURATION)); - when(ruleMock.getAction()).thenReturn(ruleMapper.readTree(ACTION_CONFIGURATION)); - - ComponentDescriptor filterComp = new ComponentDescriptor(); - filterComp.setClazz("org.thingsboard.server.extensions.core.filter.MsgTypeFilter"); - filterComp.setType(ComponentType.FILTER); - when(componentService.getComponent("org.thingsboard.server.extensions.core.filter.MsgTypeFilter")) - .thenReturn(Optional.of(filterComp)); - - ComponentDescriptor actionComp = new ComponentDescriptor(); - actionComp.setClazz("org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction"); - actionComp.setType(ComponentType.ACTION); - when(componentService.getComponent("org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction")) - .thenReturn(Optional.of(actionComp)); - - ObjectMapper pluginMapper = new ObjectMapper(); - JsonNode pluginAdditionalInfo = pluginMapper.readTree(PLUGIN_CONFIGURATION); - when(pluginMock.getConfiguration()).thenReturn(pluginAdditionalInfo); - when(pluginMock.getClazz()).thenReturn(TelemetryStoragePlugin.class.getName()); - - when(attributesService.findAll(deviceId, DataConstants.CLIENT_SCOPE)).thenReturn(Futures.immediateFuture(Collections.emptyList())); - when(attributesService.findAll(deviceId, DataConstants.SHARED_SCOPE)).thenReturn(Futures.immediateFuture(Collections.emptyList())); - when(attributesService.findAll(deviceId, DataConstants.SERVER_SCOPE)).thenReturn(Futures.immediateFuture(Collections.emptyList())); - - initActorSystem(); - Thread.sleep(1000); - actorService.process(new BasicToDeviceActorSessionMsg(device, msg)); - - // Check that device data was saved to DB; - List expected = new ArrayList<>(); - expected.add(new BasicTsKvEntry(ts, entry1)); - expected.add(new BasicTsKvEntry(ts, entry2)); - verify(tsService, Mockito.timeout(5000)).save(deviceId, expected, 0L); - } - -} diff --git a/application/src/test/java/org/thingsboard/server/actors/DummySessionID.java b/application/src/test/java/org/thingsboard/server/actors/DummySessionID.java deleted file mode 100644 index 9324e26c3b..0000000000 --- a/application/src/test/java/org/thingsboard/server/actors/DummySessionID.java +++ /dev/null @@ -1,63 +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; - -import org.thingsboard.server.common.data.id.SessionId; - -public class DummySessionID implements SessionId { - - @Override - public String toString() { - return id; - } - - private final String id; - - public DummySessionID(String id) { - this.id = id; - } - - @Override - public String toUidStr() { - return id; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((id == null) ? 0 : id.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; - DummySessionID other = (DummySessionID) obj; - if (id == null) { - if (other.id != null) - return false; - } else if (!id.equals(other.id)) - return false; - return true; - } - -} diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java index 93fe76752e..5b895b1513 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractRuleEngineControllerTest.java @@ -16,6 +16,8 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.id.EntityId; @@ -25,12 +27,22 @@ import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.dao.queue.MsgQueue; +import org.thingsboard.server.dao.rule.RuleChainService; + +import java.io.IOException; /** * Created by ashvayka on 20.03.18. */ public class AbstractRuleEngineControllerTest extends AbstractControllerTest { + @Autowired + protected RuleChainService ruleChainService; + + @Autowired + protected MsgQueue msgQueue; + protected RuleChain saveRuleChain(RuleChain ruleChain) throws Exception { return doPost("/api/ruleChain", ruleChain, RuleChain.class); } @@ -53,4 +65,13 @@ public class AbstractRuleEngineControllerTest extends AbstractControllerTest { new TypeReference>() { }, pageLink, entityId.getEntityType(), entityId.getId(), DataConstants.DEBUG_RULE_NODE, tenantId.getId()); } + + protected JsonNode getMetadata(Event outEvent) { + String metaDataStr = outEvent.getBody().get("metadata").asText(); + try { + return mapper.readTree(metaDataStr); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java index 4346538ad7..3a07d2989d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseComponentDescriptorControllerTest.java @@ -20,6 +20,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.rule.engine.filter.TbJsFilterNode; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; @@ -35,7 +36,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public abstract class BaseComponentDescriptorControllerTest extends AbstractControllerTest { - private static final int AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS = 5; + private static final int AMOUNT_OF_DEFAULT_FILTER_NODES = 4; private Tenant savedTenant; private User tenantAdmin; @@ -69,38 +70,28 @@ public abstract class BaseComponentDescriptorControllerTest extends AbstractCont @Test public void testGetByClazz() throws Exception { ComponentDescriptor descriptor = - doGet("/api/component/" + TelemetryStoragePlugin.class.getName(), ComponentDescriptor.class); + doGet("/api/component/" + TbJsFilterNode.class.getName(), ComponentDescriptor.class); Assert.assertNotNull(descriptor); Assert.assertNotNull(descriptor.getId()); Assert.assertNotNull(descriptor.getName()); Assert.assertEquals(ComponentScope.TENANT, descriptor.getScope()); - Assert.assertEquals(ComponentType.PLUGIN, descriptor.getType()); + Assert.assertEquals(ComponentType.FILTER, descriptor.getType()); Assert.assertEquals(descriptor.getClazz(), descriptor.getClazz()); } @Test public void testGetByType() throws Exception { List descriptors = readResponse( - doGet("/api/components/" + ComponentType.PLUGIN).andExpect(status().isOk()), new TypeReference>() { + doGet("/api/components/" + ComponentType.FILTER).andExpect(status().isOk()), new TypeReference>() { }); Assert.assertNotNull(descriptors); - Assert.assertEquals(AMOUNT_OF_DEFAULT_PLUGINS_DESCRIPTORS, descriptors.size()); + Assert.assertTrue(descriptors.size() >= AMOUNT_OF_DEFAULT_FILTER_NODES); for (ComponentType type : ComponentType.values()) { doGet("/api/components/" + type).andExpect(status().isOk()); } } - @Test - public void testGetActionsByType() throws Exception { - List descriptors = readResponse( - doGet("/api/components/actions/" + TelemetryStoragePlugin.class.getName()).andExpect(status().isOk()), new TypeReference>() { - }); - - Assert.assertNotNull(descriptors); - Assert.assertEquals(1, descriptors.size()); - Assert.assertEquals(TelemetryPluginAction.class.getName(), descriptors.get(0).getClazz()); - } } diff --git a/application/src/test/java/org/thingsboard/server/controller/BasePluginControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BasePluginControllerTest.java deleted file mode 100644 index 915015fcd6..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/BasePluginControllerTest.java +++ /dev/null @@ -1,232 +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.controller; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.plugin.PluginMetaData; -import org.thingsboard.server.common.data.rule.RuleMetaData; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -public abstract class BasePluginControllerTest extends AbstractControllerTest { - - private IdComparator idComparator = new IdComparator<>(); - - private final ObjectMapper mapper = new ObjectMapper(); - private Tenant savedTenant; - private User tenantAdmin; - - @Before - public void beforeTest() throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - } - - @After - public void afterTest() throws Exception { - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); - } - - @Test - public void testSavePlugin() throws Exception { - PluginMetaData plugin = new PluginMetaData(); - doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); - plugin.setName("My plugin"); - doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); - plugin.setApiToken("myplugin"); - doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); - plugin.setConfiguration(mapper.readTree("{}")); - doPost("/api/plugin", plugin).andExpect(status().isBadRequest()); - plugin.setClazz(TelemetryStoragePlugin.class.getName()); - PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); - - Assert.assertNotNull(savedPlugin); - Assert.assertNotNull(savedPlugin.getId()); - Assert.assertTrue(savedPlugin.getCreatedTime() > 0); - Assert.assertEquals(savedTenant.getId(), savedPlugin.getTenantId()); - } - - @Test - public void testFindPluginById() throws Exception { - PluginMetaData plugin = new PluginMetaData(); - plugin.setName("My plugin"); - plugin.setApiToken("myplugin"); - plugin.setConfiguration(mapper.readTree("{}")); - plugin.setClazz(TelemetryStoragePlugin.class.getName()); - - PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); - PluginMetaData foundPlugin = doGet("/api/plugin/" + savedPlugin.getId().getId().toString(), PluginMetaData.class); - Assert.assertNotNull(foundPlugin); - Assert.assertEquals(savedPlugin, foundPlugin); - } - - @Test - public void testActivatePlugin() throws Exception { - PluginMetaData plugin = new PluginMetaData(); - plugin.setName("My plugin"); - plugin.setApiToken("myplugin"); - plugin.setConfiguration(mapper.readTree("{}")); - plugin.setClazz(TelemetryStoragePlugin.class.getName()); - - PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); - - doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); - } - - @Test - public void testSuspendPlugin() throws Exception { - PluginMetaData plugin = new PluginMetaData(); - plugin.setName("My plugin"); - plugin.setApiToken("myplugin"); - plugin.setConfiguration(mapper.readTree("{}")); - plugin.setClazz(TelemetryStoragePlugin.class.getName()); - - PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); - - doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); - - RuleMetaData rule = BaseRuleControllerTest.createRuleMetaData(savedPlugin); - RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); - doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk()); - - doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/suspend").andExpect(status().isBadRequest()); - - doPost("/api/rule/" + savedRule.getId().getId().toString() + "/suspend").andExpect(status().isOk()); - - doPost("/api/plugin/" + savedPlugin.getId().getId().toString() + "/suspend").andExpect(status().isOk()); - } - - @Test - public void testDeletePluginById() throws Exception { - PluginMetaData plugin = new PluginMetaData(); - plugin.setName("My plugin"); - plugin.setApiToken("myplugin"); - plugin.setConfiguration(mapper.readTree("{}")); - plugin.setClazz(TelemetryStoragePlugin.class.getName()); - - PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); - - RuleMetaData rule = BaseRuleControllerTest.createRuleMetaData(savedPlugin); - RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); - - doDelete("/api/plugin/" + savedPlugin.getId().getId()).andExpect(status().isBadRequest()); - - doDelete("/api/rule/" + savedRule.getId().getId()).andExpect(status().isOk()); - - doDelete("/api/plugin/" + savedPlugin.getId().getId()).andExpect(status().isOk()); - doGet("/api/plugin/" + savedPlugin.getId().getId().toString()).andExpect(status().isNotFound()); - } - - @Test - public void testFindPluginByToken() throws Exception { - PluginMetaData plugin = new PluginMetaData(); - plugin.setName("My plugin"); - plugin.setApiToken("myplugin"); - plugin.setConfiguration(mapper.readTree("{}")); - plugin.setClazz(TelemetryStoragePlugin.class.getName()); - - PluginMetaData savedPlugin = doPost("/api/plugin", plugin, PluginMetaData.class); - PluginMetaData foundPlugin = doGet("/api/plugin/token/" + "myplugin", PluginMetaData.class); - Assert.assertNotNull(foundPlugin); - Assert.assertEquals(savedPlugin, foundPlugin); - } - - @Test - public void testFindCurrentTenantPlugins() throws Exception { - List plugins = testPluginsCreation("/api/plugin"); - for (PluginMetaData plugin : plugins) { - doDelete("/api/plugin/" + plugin.getId().getId()).andExpect(status().isOk()); - } - } - - @Test - public void testFindSystemPlugins() throws Exception { - loginSysAdmin(); - List plugins = testPluginsCreation("/api/plugin/system"); - for (PluginMetaData plugin : plugins) { - doDelete("/api/plugin/" + plugin.getId().getId()).andExpect(status().isOk()); - } - } - - private List testPluginsCreation(String url) throws Exception { - List plugins = new ArrayList<>(); - for (int i = 0; i < 111; i++) { - PluginMetaData plugin = new PluginMetaData(); - plugin.setName("My plugin"); - plugin.setApiToken("myplugin" + i); - plugin.setConfiguration(mapper.readTree("{}")); - plugin.setClazz(TelemetryStoragePlugin.class.getName()); - plugins.add(doPost("/api/plugin", plugin, PluginMetaData.class)); - } - - List loadedPlugins = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(23); - TextPageData pageData; - do { - pageData = doGetTypedWithPageLink(url + "?", - new TypeReference>() { - }, pageLink); - loadedPlugins.addAll(pageData.getData()); - if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); - } - } while (pageData.hasNext()); - - loadedPlugins = loadedPlugins.stream() - .filter(p -> !p.getName().equals("System Telemetry Plugin")) - .filter(p -> !p.getName().equals("Mail Sender Plugin")) - .filter(p -> !p.getName().equals("System RPC Plugin")) - .collect(Collectors.toList()); - - Collections.sort(plugins, idComparator); - Collections.sort(loadedPlugins, idComparator); - - Assert.assertEquals(plugins, loadedPlugins); - return loadedPlugins; - } -} diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseRuleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseRuleControllerTest.java deleted file mode 100644 index 9236dfec06..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/BaseRuleControllerTest.java +++ /dev/null @@ -1,247 +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.controller; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.page.TextPageData; -import org.thingsboard.server.common.data.page.TextPageLink; -import org.thingsboard.server.common.data.plugin.PluginMetaData; -import org.thingsboard.server.common.data.rule.RuleMetaData; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.extensions.core.plugin.telemetry.TelemetryStoragePlugin; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -public abstract class BaseRuleControllerTest extends AbstractControllerTest { - - private IdComparator idComparator = new IdComparator<>(); - - private static final ObjectMapper mapper = new ObjectMapper(); - private Tenant savedTenant; - private User tenantAdmin; - private PluginMetaData sysPlugin; - private PluginMetaData tenantPlugin; - - @Before - public void beforeTest() throws Exception { - loginSysAdmin(); - - sysPlugin = new PluginMetaData(); - sysPlugin.setName("Sys plugin"); - sysPlugin.setApiToken("sysplugin"); - sysPlugin.setConfiguration(mapper.readTree("{}")); - sysPlugin.setClazz(TelemetryStoragePlugin.class.getName()); - sysPlugin = doPost("/api/plugin", sysPlugin, PluginMetaData.class); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - - tenantPlugin = new PluginMetaData(); - tenantPlugin.setName("My plugin"); - tenantPlugin.setApiToken("myplugin"); - tenantPlugin.setConfiguration(mapper.readTree("{}")); - tenantPlugin.setClazz(TelemetryStoragePlugin.class.getName()); - tenantPlugin = doPost("/api/plugin", tenantPlugin, PluginMetaData.class); - } - - @After - public void afterTest() throws Exception { - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); - - doDelete("/api/plugin/" + sysPlugin.getId().getId()).andExpect(status().isOk()); - } - - @Test - public void testSaveRule() throws Exception { - RuleMetaData rule = new RuleMetaData(); - doPost("/api/rule", rule).andExpect(status().isBadRequest()); - rule.setName("My Rule"); - doPost("/api/rule", rule).andExpect(status().isBadRequest()); - rule.setPluginToken(tenantPlugin.getApiToken()); - doPost("/api/rule", rule).andExpect(status().isBadRequest()); - rule.setFilters(mapper.readTree("[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", " + - "\"name\":\"TelemetryFilter\", " + - "\"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]")); - doPost("/api/rule", rule).andExpect(status().isBadRequest()); - rule.setAction(mapper.readTree("{\"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", \"configuration\":{\"timeUnit\":\"DAYS\", \"ttlValue\":1}}")); - - RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); - Assert.assertNotNull(savedRule); - Assert.assertNotNull(savedRule.getId()); - Assert.assertTrue(savedRule.getCreatedTime() > 0); - Assert.assertEquals(savedTenant.getId(), savedRule.getTenantId()); - } - - @Test - public void testFindRuleById() throws Exception { - RuleMetaData rule = createRuleMetaData(tenantPlugin); - RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); - - RuleMetaData foundRule = doGet("/api/rule/" + savedRule.getId().getId().toString(), RuleMetaData.class); - Assert.assertNotNull(foundRule); - Assert.assertEquals(savedRule, foundRule); - } - - @Test - public void testFindRuleByPluginToken() throws Exception { - RuleMetaData rule = createRuleMetaData(tenantPlugin); - RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); - - List foundRules = doGetTyped("/api/rule/token/" + savedRule.getPluginToken(), - new TypeReference>() { - }); - Assert.assertNotNull(foundRules); - Assert.assertEquals(1, foundRules.size()); - Assert.assertEquals(savedRule, foundRules.get(0)); - } - - @Test - public void testActivateRule() throws Exception { - RuleMetaData rule = createRuleMetaData(tenantPlugin); - RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); - - doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isBadRequest()); - - doPost("/api/plugin/" + tenantPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); - - doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk()); - } - - @Test - public void testSuspendRule() throws Exception { - RuleMetaData rule = createRuleMetaData(tenantPlugin); - RuleMetaData savedRule = doPost("/api/rule", rule, RuleMetaData.class); - - doPost("/api/plugin/" + tenantPlugin.getId().getId().toString() + "/activate").andExpect(status().isOk()); - doPost("/api/rule/" + savedRule.getId().getId().toString() + "/activate").andExpect(status().isOk()); - doPost("/api/rule/" + savedRule.getId().getId().toString() + "/suspend").andExpect(status().isOk()); - } - - @Test - public void testFindSystemRules() throws Exception { - loginSysAdmin(); - List rules = testRulesCreation("/api/rule/system", sysPlugin); - for (RuleMetaData rule : rules) { - doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk()); - } - loginTenantAdmin(); - } - - @Test - public void testFindCurrentTenantPlugins() throws Exception { - List rules = testRulesCreation("/api/rule", tenantPlugin); - for (RuleMetaData rule : rules) { - doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk()); - } - } - - @Test - public void testFindTenantPlugins() throws Exception { - List rules = testRulesCreation("/api/rule", tenantPlugin); - loginSysAdmin(); - List loadedRules = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(3); - TextPageData pageData; - do { - pageData = doGetTypedWithPageLink("/api/rule/tenant/" + savedTenant.getId().getId().toString() + "?", - new TypeReference>() { - }, pageLink); - loadedRules.addAll(pageData.getData()); - if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); - } - } while (pageData.hasNext()); - - Collections.sort(rules, idComparator); - Collections.sort(loadedRules, idComparator); - - Assert.assertEquals(rules, loadedRules); - - for (RuleMetaData rule : rules) { - doDelete("/api/rule/" + rule.getId().getId()).andExpect(status().isOk()); - } - } - - private List testRulesCreation(String url, PluginMetaData plugin) throws Exception { - List rules = new ArrayList<>(); - for (int i = 0; i < 6; i++) { - RuleMetaData rule = createRuleMetaData(plugin); - rule.setPluginToken(plugin.getApiToken()); - rule.setName(rule.getName() + i); - rules.add(doPost("/api/rule", rule, RuleMetaData.class)); - } - - List loadedRules = new ArrayList<>(); - TextPageLink pageLink = new TextPageLink(3); - TextPageData pageData; - do { - pageData = doGetTypedWithPageLink(url + "?", - new TypeReference>() { - }, pageLink); - loadedRules.addAll(pageData.getData()); - if (pageData.hasNext()) { - pageLink = pageData.getNextPageLink(); - } - } while (pageData.hasNext()); - - loadedRules = loadedRules.stream().filter(p -> !p.getName().equals("System Telemetry Rule")).collect(Collectors.toList()); - - Collections.sort(rules, idComparator); - Collections.sort(loadedRules, idComparator); - - Assert.assertEquals(rules, loadedRules); - return loadedRules; - } - - public static RuleMetaData createRuleMetaData(PluginMetaData plugin) throws IOException { - RuleMetaData rule = new RuleMetaData(); - rule.setName("My Rule"); - rule.setPluginToken(plugin.getApiToken()); - rule.setFilters(mapper.readTree("[{\"clazz\":\"org.thingsboard.server.extensions.core.filter.MsgTypeFilter\", " + - "\"name\":\"TelemetryFilter\", " + - "\"configuration\": {\"messageTypes\":[\"POST_TELEMETRY\",\"POST_ATTRIBUTES\",\"GET_ATTRIBUTES\"]}}]")); - rule.setAction(mapper.readTree("{\"clazz\":\"org.thingsboard.server.extensions.core.action.telemetry.TelemetryPluginAction\", \"name\":\"TelemetryMsgConverterAction\", " + - "\"configuration\":{\"timeUnit\":\"DAYS\", \"ttlValue\":1}}")); - return rule; - } -} diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/PluginControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/PluginControllerSqlTest.java deleted file mode 100644 index fbb63385e7..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/sql/PluginControllerSqlTest.java +++ /dev/null @@ -1,26 +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.controller.sql; - -import org.thingsboard.server.controller.BasePluginControllerTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -/** - * Created by Valerii Sosliuk on 6/28/2017. - */ -@DaoSqlTest -public class PluginControllerSqlTest extends BasePluginControllerTest { -} diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java index 7c9c0586f0..f3d69bacd5 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/sql/MqttServerSideRpcSqlIntegrationTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.mqtt.rpc.sql; -import org.thingsboard.server.dao.service.DaoNoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest; diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java new file mode 100644 index 0000000000..bffe4913ef --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java @@ -0,0 +1,42 @@ +/** + * 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.rules; + +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; +import org.junit.ClassRule; +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.runner.RunWith; +import org.thingsboard.server.dao.CustomCassandraCQLUnit; +import org.thingsboard.server.dao.CustomSqlUnit; + +import java.util.Arrays; + +@RunWith(ClasspathSuite.class) +@ClasspathSuite.ClassnameFilters({ + "org.thingsboard.server.rules.flow.nosql.*Test", + "org.thingsboard.server.rules.lifecycle.nosql.*Test" +}) +public class RuleEngineNoSqlTestSuite { + + @ClassRule + public static CustomCassandraCQLUnit cassandraUnit = + new CustomCassandraCQLUnit( + Arrays.asList( + new ClassPathCQLDataSet("cassandra/schema.cql", false, false), + new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)), + "cassandra-test.yaml", 30000l); + +} diff --git a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java index 65b4293490..7b13e2fd54 100644 --- a/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/rules/RuleEngineSqlTestSuite.java @@ -24,7 +24,8 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.rules.flow.*Test"}) + "org.thingsboard.server.rules.flow.sql.*Test", + "org.thingsboard.server.rules.lifecycle.sql.*Test"}) public class RuleEngineSqlTestSuite { @ClassRule diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java index f88eb24336..a294816f49 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java @@ -16,6 +16,8 @@ package org.thingsboard.server.rules.flow; import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Lists; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.junit.After; @@ -28,6 +30,7 @@ import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -40,8 +43,10 @@ import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.rule.RuleChainService; +import java.io.IOException; import java.util.Arrays; import java.util.Collections; +import java.util.List; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -60,9 +65,6 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule @Autowired protected AttributesService attributesService; - @Autowired - protected RuleChainService ruleChainService; - @Before public void beforeTest() throws Exception { loginSysAdmin(); @@ -71,6 +73,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule tenant.setTitle("My tenant"); savedTenant = doPost("/api/tenant", tenant, Tenant.class); Assert.assertNotNull(savedTenant); + ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId()); tenantAdmin = new User(); tenantAdmin.setAuthority(Authority.TENANT_ADMIN); @@ -149,7 +152,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule "CUSTOM", device.getId(), new TbMsgMetaData(), - "{}"); + "{}", null, null, 0L); actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)); Thread.sleep(3000); @@ -166,7 +169,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId()); Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); - Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText()); + Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); RuleChain finalRuleChain = ruleChain; RuleNode lastRuleNode = metaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get(); @@ -183,8 +186,131 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule Assert.assertEquals(lastRuleNode.getId(), outEvent.getEntityId()); Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); - Assert.assertEquals("serverAttributeValue1", outEvent.getBody().get("metadata").get("ss.serverAttributeKey1").asText()); - Assert.assertEquals("serverAttributeValue2", outEvent.getBody().get("metadata").get("ss.serverAttributeKey2").asText()); + Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); + Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText()); + + List unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(ruleChain.getId().getId(), 0L)); + Assert.assertEquals(0, unAckMsgList.size()); + } + + @Test + public void testTwoRuleChainsWithTwoRules() throws Exception { + // Creating Rule Chain + RuleChain rootRuleChain = new RuleChain(); + rootRuleChain.setName("Root Rule Chain"); + rootRuleChain.setTenantId(savedTenant.getId()); + rootRuleChain.setRoot(true); + rootRuleChain.setDebugMode(true); + rootRuleChain = saveRuleChain(rootRuleChain); + Assert.assertNull(rootRuleChain.getFirstRuleNodeId()); + + // Creating Rule Chain + RuleChain secondaryRuleChain = new RuleChain(); + secondaryRuleChain.setName("Secondary Rule Chain"); + secondaryRuleChain.setTenantId(savedTenant.getId()); + secondaryRuleChain.setRoot(false); + secondaryRuleChain.setDebugMode(true); + secondaryRuleChain = saveRuleChain(secondaryRuleChain); + Assert.assertNull(secondaryRuleChain.getFirstRuleNodeId()); + + RuleChainMetaData rootMetaData = new RuleChainMetaData(); + rootMetaData.setRuleChainId(rootRuleChain.getId()); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("Simple Rule Node 1"); + ruleNode1.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode1.setDebugMode(true); + TbGetAttributesNodeConfiguration configuration1 = new TbGetAttributesNodeConfiguration(); + configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); + ruleNode1.setConfiguration(mapper.valueToTree(configuration1)); + + rootMetaData.setNodes(Collections.singletonList(ruleNode1)); + rootMetaData.setFirstNodeIndex(0); + rootMetaData.addRuleChainConnectionInfo(0, secondaryRuleChain.getId(), "Success", mapper.createObjectNode()); + rootMetaData = saveRuleChainMetaData(rootMetaData); + Assert.assertNotNull(rootMetaData); + + rootRuleChain = getRuleChain(rootRuleChain.getId()); + Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId()); + + + RuleChainMetaData secondaryMetaData = new RuleChainMetaData(); + secondaryMetaData.setRuleChainId(secondaryRuleChain.getId()); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("Simple Rule Node 2"); + ruleNode2.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode2.setDebugMode(true); + TbGetAttributesNodeConfiguration configuration2 = new TbGetAttributesNodeConfiguration(); + configuration2.setServerAttributeNames(Collections.singletonList("serverAttributeKey2")); + ruleNode2.setConfiguration(mapper.valueToTree(configuration2)); + + secondaryMetaData.setNodes(Collections.singletonList(ruleNode2)); + secondaryMetaData.setFirstNodeIndex(0); + secondaryMetaData = saveRuleChainMetaData(secondaryMetaData); + Assert.assertNotNull(secondaryMetaData); + + // Saving the device + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + device = doPost("/api/device", device, Device.class); + + attributesService.save(device.getId(), DataConstants.SERVER_SCOPE, + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))); + attributesService.save(device.getId(), DataConstants.SERVER_SCOPE, + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))); + + + Thread.sleep(1000); + + // Pushing Message to the system + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), + "CUSTOM", + device.getId(), + new TbMsgMetaData(), + "{}", null, null, 0L); + actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)); + + Thread.sleep(3000); + + TimePageData events = getDebugEvents(savedTenant.getId(), rootRuleChain.getFirstRuleNodeId(), 1000); + + Assert.assertEquals(2, events.getData().size()); + + Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); + Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), inEvent.getEntityId()); + Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText()); + + Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get(); + Assert.assertEquals(rootRuleChain.getFirstRuleNodeId(), outEvent.getEntityId()); + Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); + + Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); + + RuleChain finalRuleChain = rootRuleChain; + RuleNode lastRuleNode = secondaryMetaData.getNodes().stream().filter(node -> !node.getId().equals(finalRuleChain.getFirstRuleNodeId())).findFirst().get(); + + events = getDebugEvents(savedTenant.getId(), lastRuleNode.getId(), 1000); + + Assert.assertEquals(2, events.getData().size()); + + inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); + Assert.assertEquals(lastRuleNode.getId(), inEvent.getEntityId()); + Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText()); + + outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get(); + Assert.assertEquals(lastRuleNode.getId(), outEvent.getEntityId()); + Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); + + Assert.assertEquals("serverAttributeValue1", getMetadata(outEvent).get("ss_serverAttributeKey1").asText()); + Assert.assertEquals("serverAttributeValue2", getMetadata(outEvent).get("ss_serverAttributeKey2").asText()); + + List unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(rootRuleChain.getId().getId(), 0L)); + Assert.assertEquals(0, unAckMsgList.size()); + + unAckMsgList = Lists.newArrayList(msgQueue.findUnprocessed(secondaryRuleChain.getId().getId(), 0L)); + Assert.assertEquals(0, unAckMsgList.size()); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/PluginControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java similarity index 72% rename from application/src/test/java/org/thingsboard/server/controller/nosql/PluginControllerNoSqlTest.java rename to application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java index 48815e6057..d073a1de4a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/PluginControllerNoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/nosql/RuleEngineFlowNoSqlIntegrationTest.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.controller.nosql; +package org.thingsboard.server.rules.flow.nosql; -import org.thingsboard.server.controller.BasePluginControllerTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; +import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; /** - * Created by Valerii Sosliuk on 6/28/2017. + * Created by Valerii Sosliuk on 8/22/2017. */ @DaoNoSqlTest -public class PluginControllerNoSqlTest extends BasePluginControllerTest { +public class RuleEngineFlowNoSqlIntegrationTest extends AbstractRuleEngineFlowIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java similarity index 87% rename from application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java index 18a164ef33..24f288ca59 100644 --- a/application/src/test/java/org/thingsboard/server/rules/flow/RuleEngineFlowSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/flow/sql/RuleEngineFlowSqlIntegrationTest.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.rules.flow; +package org.thingsboard.server.rules.flow.sql; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.mqtt.rpc.AbstractMqttServerSideRpcIntegrationTest; +import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; /** * Created by Valerii Sosliuk on 8/22/2017. diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java index 22d79f090f..0ea6ff4aec 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.rules.lifecycle; import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Assert; @@ -42,6 +43,7 @@ import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg; import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; import org.thingsboard.server.dao.attributes.AttributesService; +import java.io.IOException; import java.util.Collections; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -69,6 +71,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac tenant.setTitle("My tenant"); savedTenant = doPost("/api/tenant", tenant, Tenant.class); Assert.assertNotNull(savedTenant); + ruleChainService.deleteRuleChainsByTenantId(savedTenant.getId()); tenantAdmin = new User(); tenantAdmin.setAuthority(Authority.TENANT_ADMIN); @@ -135,7 +138,8 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac "CUSTOM", device.getId(), new TbMsgMetaData(), - "{}"); + "{}", + null, null, 0L); actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)); Thread.sleep(3000); @@ -152,7 +156,75 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId()); Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); - Assert.assertEquals("serverAttributeValue", outEvent.getBody().get("metadata").get("ss.serverAttributeKey").asText()); + Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText()); + } + + @Test + public void testRuleChainWithOneRuleAndMsgFromQueue() throws Exception { + // Creating Rule Chain + RuleChain ruleChain = new RuleChain(); + ruleChain.setName("Simple Rule Chain"); + ruleChain.setTenantId(savedTenant.getId()); + ruleChain.setRoot(true); + ruleChain.setDebugMode(true); + ruleChain = saveRuleChain(ruleChain); + Assert.assertNull(ruleChain.getFirstRuleNodeId()); + + // Saving the device + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + device = doPost("/api/device", device, Device.class); + + attributesService.save(device.getId(), DataConstants.SERVER_SCOPE, + Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey", "serverAttributeValue"), System.currentTimeMillis()))); + + // Pushing Message to the system + TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), + "CUSTOM", + device.getId(), + new TbMsgMetaData(), + "{}", + ruleChain.getId(), null, 0L); + msgQueue.put(tbMsg, ruleChain.getId().getId(), 0L); + + Thread.sleep(1000); + + RuleChainMetaData metaData = new RuleChainMetaData(); + metaData.setRuleChainId(ruleChain.getId()); + + RuleNode ruleNode = new RuleNode(); + ruleNode.setName("Simple Rule Node"); + ruleNode.setType(org.thingsboard.rule.engine.metadata.TbGetAttributesNode.class.getName()); + ruleNode.setDebugMode(true); + TbGetAttributesNodeConfiguration configuration = new TbGetAttributesNodeConfiguration(); + configuration.setServerAttributeNames(Collections.singletonList("serverAttributeKey")); + ruleNode.setConfiguration(mapper.valueToTree(configuration)); + + metaData.setNodes(Collections.singletonList(ruleNode)); + metaData.setFirstNodeIndex(0); + + metaData = saveRuleChainMetaData(metaData); + Assert.assertNotNull(metaData); + + ruleChain = getRuleChain(ruleChain.getId()); + Assert.assertNotNull(ruleChain.getFirstRuleNodeId()); + + Thread.sleep(3000); + + TimePageData events = getDebugEvents(savedTenant.getId(), ruleChain.getFirstRuleNodeId(), 1000); + + Assert.assertEquals(2, events.getData().size()); + + Event inEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.IN)).findFirst().get(); + Assert.assertEquals(ruleChain.getFirstRuleNodeId(), inEvent.getEntityId()); + Assert.assertEquals(device.getId().getId().toString(), inEvent.getBody().get("entityId").asText()); + + Event outEvent = events.getData().stream().filter(e -> e.getBody().get("type").asText().equals(DataConstants.OUT)).findFirst().get(); + Assert.assertEquals(ruleChain.getFirstRuleNodeId(), outEvent.getEntityId()); + Assert.assertEquals(device.getId().getId().toString(), outEvent.getBody().get("entityId").asText()); + + Assert.assertEquals("serverAttributeValue", getMetadata(outEvent).get("ss_serverAttributeKey").asText()); } } diff --git a/application/src/test/java/org/thingsboard/server/controller/nosql/RuleControllerNoSqlTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java similarity index 70% rename from application/src/test/java/org/thingsboard/server/controller/nosql/RuleControllerNoSqlTest.java rename to application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java index 0557d5d3d9..6f66359f62 100644 --- a/application/src/test/java/org/thingsboard/server/controller/nosql/RuleControllerNoSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/nosql/RuleEngineLifecycleNoSqlIntegrationTest.java @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.controller.nosql; +package org.thingsboard.server.rules.lifecycle.nosql; -import org.thingsboard.server.controller.BaseRuleControllerTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; +import org.thingsboard.server.rules.lifecycle.AbstractRuleEngineLifecycleIntegrationTest; /** - * Created by Valerii Sosliuk on 6/28/2017. + * Created by Valerii Sosliuk on 8/22/2017. */ @DaoNoSqlTest -public class RuleControllerNoSqlTest extends BaseRuleControllerTest { +public class RuleEngineLifecycleNoSqlIntegrationTest extends AbstractRuleEngineLifecycleIntegrationTest { } diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java similarity index 86% rename from application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java index 004958b2ea..c74c515124 100644 --- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/RuleEngineLifecycleSqlIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/sql/RuleEngineLifecycleSqlIntegrationTest.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.rules.lifecycle; +package org.thingsboard.server.rules.lifecycle.sql; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.rules.flow.AbstractRuleEngineFlowIntegrationTest; +import org.thingsboard.server.rules.lifecycle.AbstractRuleEngineLifecycleIntegrationTest; /** * Created by Valerii Sosliuk on 8/22/2017. diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java index e6a48e2838..1981287f6d 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsEngineTest.java @@ -42,7 +42,7 @@ public class NashornJsEngineTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L); TbMsg actual = scriptEngine.executeUpdate(msg); assertEquals("70", actual.getMetaData().getValue("temp")); @@ -57,7 +57,7 @@ public class NashornJsEngineTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L); TbMsg actual = scriptEngine.executeUpdate(msg); assertEquals("94", actual.getMetaData().getValue("newAttr")); @@ -72,7 +72,7 @@ public class NashornJsEngineTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L); TbMsg actual = scriptEngine.executeUpdate(msg); @@ -89,7 +89,7 @@ public class NashornJsEngineTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L); assertFalse(scriptEngine.executeFilter(msg)); } @@ -102,7 +102,7 @@ public class NashornJsEngineTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L); assertTrue(scriptEngine.executeFilter(msg)); } @@ -122,7 +122,7 @@ public class NashornJsEngineTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L); Set actual = scriptEngine.executeSwitch(msg); assertEquals(Sets.newHashSet("one"), actual); } @@ -143,7 +143,7 @@ public class NashornJsEngineTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, null, null, 0L); Set actual = scriptEngine.executeSwitch(msg); assertEquals(Sets.newHashSet("one", "three"), actual); } diff --git a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java index cfa0c586a9..97c67491f1 100644 --- a/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/system/SystemSqlTestSuite.java @@ -35,5 +35,4 @@ public class SystemSqlTestSuite { "sql/drop-all-tables.sql", "sql-test.properties"); - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 7d4e4807c9..f4e95596e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -45,4 +45,9 @@ public class DataConstants { public static final String IN = "IN"; public static final String OUT = "OUT"; + public static final String INACTIVITY_EVENT = "INACTIVITY_EVENT"; + public static final String CONNECT_EVENT = "CONNECT_EVENT"; + public static final String DISCONNECT_EVENT = "DISCONNECT_EVENT"; + public static final String ACTIVITY_EVENT = "ACTIVITY_EVENT"; + } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/cmd/RpcRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java similarity index 92% rename from extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/cmd/RpcRequest.java rename to common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java index 31a6911be9..a915df8ffc 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/cmd/RpcRequest.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcRequest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.core.plugin.rpc.cmd; +package org.thingsboard.server.common.data.rpc; import lombok.Data; diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/ToDeviceRpcRequestBody.java similarity index 93% rename from extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java rename to common/data/src/main/java/org/thingsboard/server/common/data/rpc/ToDeviceRpcRequestBody.java index b6503363bd..73eb83e7f7 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequestBody.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/ToDeviceRpcRequestBody.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.api.plugins.msg; +package org.thingsboard.server.common.data.rpc; import lombok.Data; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java index fbc1103dee..808166ba99 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleNode.java @@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.SearchTextBasedWithAdditionalInfo; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; @@ -32,6 +33,7 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo impl private static final long serialVersionUID = -5656679015121235465L; + private RuleChainId ruleChainId; private String type; private String name; private boolean debugMode; @@ -49,8 +51,10 @@ public class RuleNode extends SearchTextBasedWithAdditionalInfo impl public RuleNode(RuleNode ruleNode) { super(ruleNode); + this.ruleChainId = ruleNode.getRuleChainId(); this.type = ruleNode.getType(); this.name = ruleNode.getName(); + this.debugMode = ruleNode.isDebugMode(); this.setConfiguration(ruleNode.getConfiguration()); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java index f8f204403a..d63456ea67 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java @@ -18,32 +18,40 @@ package org.thingsboard.server.common.msg; /** * Created by ashvayka on 15.03.18. */ +//TODO: add all "See" references public enum MsgType { + /** + * ADDED/UPDATED/DELETED events for server nodes. + * + * See {@link org.thingsboard.server.common.msg.cluster.ClusterEventMsg} + */ + CLUSTER_EVENT_MSG, + /** * ADDED/UPDATED/DELETED events for main entities. * - * @See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg} + * See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg} */ COMPONENT_LIFE_CYCLE_MSG, /** * Misc messages from the REST API/SERVICE layer to the new rule engine. * - * @See {@link org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg} + * See {@link org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg} */ SERVICE_TO_RULE_ENGINE_MSG, - - SESSION_TO_DEVICE_ACTOR_MSG, - DEVICE_ACTOR_TO_SESSION_MSG, - - /** * Message that is sent by RuleChainActor to RuleActor with command to process TbMsg. */ RULE_CHAIN_TO_RULE_MSG, + /** + * Message that is sent by RuleChainActor to other RuleChainActor with command to process TbMsg. + */ + RULE_CHAIN_TO_RULE_CHAIN_MSG, + /** * Message that is sent by RuleActor to RuleChainActor with command to process TbMsg by next nodes in chain. */ @@ -59,4 +67,35 @@ public enum MsgType { */ RULE_TO_SELF_MSG, + /** + * Message that is sent by Session Actor to Device Actor. Represents messages from the device itself. + */ + DEVICE_SESSION_TO_DEVICE_ACTOR_MSG, + + DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG, + + DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG, + + DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG, + + DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG, + + SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG, + + DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG, + + DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG, + + DEVICE_ACTOR_QUEUE_TIMEOUT_MSG, + + /** + * Message that is sent from the Device Actor to Rule Engine. Requires acknowledgement + */ + DEVICE_ACTOR_TO_RULE_ENGINE_MSG, + + /** + * Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue. + */ + RULE_ENGINE_QUEUE_PUT_ACK_MSG; + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 1c7de3bf10..2b3087286d 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -15,12 +15,13 @@ */ package org.thingsboard.server.common.msg; -import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.gen.MsgProtos; import java.io.Serializable; @@ -41,22 +42,40 @@ public final class TbMsg implements Serializable { private final TbMsgDataType dataType; private final String data; - public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, String data) { + //The following fields are not persisted to DB, because they can always be recovered from the context; + private final RuleChainId ruleChainId; + private final RuleNodeId ruleNodeId; + private final long clusterPartition; + + public TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, String data, + RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) { this.id = id; this.type = type; this.originator = originator; this.metaData = metaData; - this.dataType = TbMsgDataType.JSON; this.data = data; + this.dataType = TbMsgDataType.JSON; + this.ruleChainId = ruleChainId; + this.ruleNodeId = ruleNodeId; + this.clusterPartition = clusterPartition; } public static ByteBuffer toBytes(TbMsg msg) { MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); builder.setId(msg.getId().toString()); builder.setType(msg.getType()); - if (msg.getOriginator() != null) { - builder.setEntityType(msg.getOriginator().getEntityType().name()); - builder.setEntityId(msg.getOriginator().getId().toString()); + builder.setEntityType(msg.getOriginator().getEntityType().name()); + builder.setEntityIdMSB(msg.getOriginator().getId().getMostSignificantBits()); + builder.setEntityIdLSB(msg.getOriginator().getId().getLeastSignificantBits()); + + if (msg.getRuleChainId() != null) { + builder.setRuleChainIdMSB(msg.getRuleChainId().getId().getMostSignificantBits()); + builder.setRuleChainIdLSB(msg.getRuleChainId().getId().getLeastSignificantBits()); + } + + if (msg.getRuleNodeId() != null) { + builder.setRuleNodeIdMSB(msg.getRuleNodeId().getId().getMostSignificantBits()); + builder.setRuleNodeIdLSB(msg.getRuleNodeId().getId().getLeastSignificantBits()); } if (msg.getMetaData() != null) { @@ -73,15 +92,21 @@ public final class TbMsg implements Serializable { try { MsgProtos.TbMsgProto proto = MsgProtos.TbMsgProto.parseFrom(buffer.array()); TbMsgMetaData metaData = new TbMsgMetaData(proto.getMetaData().getDataMap()); - EntityId entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); + EntityId entityId = EntityIdFactory.getByTypeAndUuid(proto.getEntityType(), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())); + RuleChainId ruleChainId = new RuleChainId(new UUID(proto.getRuleChainIdMSB(), proto.getRuleChainIdLSB())); + RuleNodeId ruleNodeId = null; + if(proto.getRuleNodeIdMSB() != 0L && proto.getRuleNodeIdLSB() != 0L) { + ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); + } TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; - return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData()); + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, proto.getClusterPartition()); } catch (InvalidProtocolBufferException e) { throw new IllegalStateException("Could not parse protobuf for TbMsg", e); } } - public TbMsg copy() { - return new TbMsg(id, type, originator, metaData.copy(), dataType, data); + public TbMsg copy(UUID newId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, long clusterPartition) { + return new TbMsg(newId, type, originator, metaData, dataType, data, ruleChainId, ruleNodeId, clusterPartition); } + } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java index 4b7314c233..e157aaa0d4 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java @@ -31,10 +31,10 @@ import java.util.concurrent.ConcurrentHashMap; @NoArgsConstructor public final class TbMsgMetaData implements Serializable { - private Map data = new ConcurrentHashMap<>(); + private final Map data = new ConcurrentHashMap<>(); public TbMsgMetaData(Map data) { - this.data = data; + this.data.putAll(data); } public String getValue(String key) { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java index 67f4de7066..7d157f66a9 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ClusterEventMsg.java @@ -16,14 +16,20 @@ package org.thingsboard.server.common.msg.cluster; import lombok.Data; +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.TbActorMsg; /** * @author Andrew Shvayka */ @Data -public final class ClusterEventMsg { +public final class ClusterEventMsg implements TbActorMsg { private final ServerAddress serverAddress; private final boolean added; + @Override + public MsgType getMsgType() { + return MsgType.CLUSTER_EVENT_MSG; + } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java index 6894ac54eb..cadaf3ce56 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesSubscribeMsg.java @@ -16,14 +16,14 @@ package org.thingsboard.server.common.msg.core; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka */ public class AttributesSubscribeMsg implements FromDeviceMsg { @Override - public MsgType getMsgType() { - return MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java index e3fcd6fc79..c98ad2ae26 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUnsubscribeMsg.java @@ -16,14 +16,15 @@ package org.thingsboard.server.common.msg.core; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka */ public class AttributesUnsubscribeMsg implements FromDeviceMsg { @Override - public MsgType getMsgType() { - return MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java index 13294891ed..1f0f9ddc99 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateNotification.java @@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core; import lombok.ToString; import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; @ToString @@ -36,9 +37,8 @@ public class AttributesUpdateNotification implements ToDeviceMsg { return true; } - @Override - public MsgType getMsgType() { - return MsgType.ATTRIBUTES_UPDATE_NOTIFICATION; + public SessionMsgType getSessionMsgType() { + return SessionMsgType.ATTRIBUTES_UPDATE_NOTIFICATION; } public AttributesKVMsg getData() { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/UpdateAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateRequest.java similarity index 93% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/UpdateAttributesRequest.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateRequest.java index a2989624dd..d4cb4b1ae6 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/UpdateAttributesRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/AttributesUpdateRequest.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.msg.session.FromDeviceMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; -public interface UpdateAttributesRequest extends FromDeviceRequestMsg { +public interface AttributesUpdateRequest extends FromDeviceRequestMsg { Set getAttributes(); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicUpdateAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicAttributesUpdateRequest.java similarity index 76% rename from common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicUpdateAttributesRequest.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicAttributesUpdateRequest.java index 994fe5e5d0..76efca0f07 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicUpdateAttributesRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicAttributesUpdateRequest.java @@ -20,19 +20,19 @@ import java.util.LinkedHashSet; import java.util.Set; import org.thingsboard.server.common.data.kv.AttributeKvEntry; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; -public class BasicUpdateAttributesRequest extends BasicRequest implements UpdateAttributesRequest { +public class BasicAttributesUpdateRequest extends BasicRequest implements AttributesUpdateRequest { private static final long serialVersionUID = 1L; private final Set data; - public BasicUpdateAttributesRequest() { + public BasicAttributesUpdateRequest() { this(DEFAULT_REQUEST_ID); } - public BasicUpdateAttributesRequest(Integer requestId) { + public BasicAttributesUpdateRequest(Integer requestId) { super(requestId); this.data = new LinkedHashSet<>(); } @@ -46,8 +46,8 @@ public class BasicUpdateAttributesRequest extends BasicRequest implements Update } @Override - public MsgType getMsgType() { - return MsgType.POST_ATTRIBUTES_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.POST_ATTRIBUTES_REQUEST; } @Override @@ -57,7 +57,7 @@ public class BasicUpdateAttributesRequest extends BasicRequest implements Update @Override public String toString() { - return "BasicUpdateAttributesRequest [data=" + data + "]"; + return "BasicAttributesUpdateRequest [data=" + data + "]"; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java index ef772c3f17..3ee04ba216 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicCommandAckResponse.java @@ -15,26 +15,27 @@ */ package org.thingsboard.server.common.msg.core; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; public class BasicCommandAckResponse extends BasicResponseMsg implements StatusCodeResponse { private static final long serialVersionUID = 1L; - public static BasicCommandAckResponse onSuccess(MsgType requestMsgType, Integer requestId) { + public static BasicCommandAckResponse onSuccess(SessionMsgType requestMsgType, Integer requestId) { return BasicCommandAckResponse.onSuccess(requestMsgType, requestId, 200); } - public static BasicCommandAckResponse onSuccess(MsgType requestMsgType, Integer requestId, Integer code) { + public static BasicCommandAckResponse onSuccess(SessionMsgType requestMsgType, Integer requestId, Integer code) { return new BasicCommandAckResponse(requestMsgType, requestId, true, null, code); } - public static BasicCommandAckResponse onError(MsgType requestMsgType, Integer requestId, Exception error) { + public static BasicCommandAckResponse onError(SessionMsgType requestMsgType, Integer requestId, Exception error) { return new BasicCommandAckResponse(requestMsgType, requestId, false, error, null); } - private BasicCommandAckResponse(MsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { - super(requestMsgType, requestId, MsgType.TO_DEVICE_RPC_RESPONSE_ACK, success, error, code); + private BasicCommandAckResponse(SessionMsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { + super(requestMsgType, requestId, SessionMsgType.TO_DEVICE_RPC_RESPONSE_ACK, success, error, code); } @Override diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java index b431c14d28..e0f6d7ecc4 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesRequest.java @@ -16,7 +16,8 @@ package org.thingsboard.server.common.msg.core; import lombok.ToString; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import java.util.Collections; import java.util.Optional; @@ -41,8 +42,8 @@ public class BasicGetAttributesRequest extends BasicRequest implements GetAttrib } @Override - public MsgType getMsgType() { - return MsgType.GET_ATTRIBUTES_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.GET_ATTRIBUTES_REQUEST; } @Override diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java index 5072de25f7..e3eb15d807 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicGetAttributesResponse.java @@ -17,23 +17,24 @@ package org.thingsboard.server.common.msg.core; import lombok.ToString; import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; @ToString public class BasicGetAttributesResponse extends BasicResponseMsg implements GetAttributesResponse { private static final long serialVersionUID = 1L; - public static BasicGetAttributesResponse onSuccess(MsgType requestMsgType, int requestId, AttributesKVMsg code) { + public static BasicGetAttributesResponse onSuccess(SessionMsgType requestMsgType, int requestId, AttributesKVMsg code) { return new BasicGetAttributesResponse(requestMsgType, requestId, true, null, code); } - public static BasicGetAttributesResponse onError(MsgType requestMsgType, int requestId, Exception error) { + public static BasicGetAttributesResponse onError(SessionMsgType requestMsgType, int requestId, Exception error) { return new BasicGetAttributesResponse(requestMsgType, requestId, false, error, null); } - private BasicGetAttributesResponse(MsgType requestMsgType, int requestId, boolean success, Exception error, AttributesKVMsg code) { - super(requestMsgType, requestId, MsgType.GET_ATTRIBUTES_RESPONSE, success, error, code); + private BasicGetAttributesResponse(SessionMsgType requestMsgType, int requestId, boolean success, Exception error, AttributesKVMsg code) { + super(requestMsgType, requestId, SessionMsgType.GET_ATTRIBUTES_RESPONSE, success, error, code); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java index caaa15cb1b..c61d8e5e03 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicResponseMsg.java @@ -18,32 +18,33 @@ package org.thingsboard.server.common.msg.core; import java.io.Serializable; import java.util.Optional; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; public class BasicResponseMsg implements ResponseMsg { private static final long serialVersionUID = 1L; - private final MsgType requestMsgType; + private final SessionMsgType requestMsgType; private final Integer requestId; - private final MsgType msgType; + private final SessionMsgType sessionMsgType; private final boolean success; private final T data; private final Exception error; - protected BasicResponseMsg(MsgType requestMsgType, Integer requestId, MsgType msgType, boolean success, Exception error, T data) { + protected BasicResponseMsg(SessionMsgType requestMsgType, Integer requestId, SessionMsgType sessionMsgType, boolean success, Exception error, T data) { super(); this.requestMsgType = requestMsgType; this.requestId = requestId; - this.msgType = msgType; + this.sessionMsgType = sessionMsgType; this.success = success; this.error = error; this.data = data; } @Override - public MsgType getRequestMsgType() { + public SessionMsgType getRequestMsgType() { return requestMsgType; } @@ -72,8 +73,7 @@ public class BasicResponseMsg implements ResponseMsg return "BasicResponseMsg [success=" + success + ", data=" + data + ", error=" + error + "]"; } - @Override - public MsgType getMsgType() { - return msgType; + public SessionMsgType getSessionMsgType() { + return sessionMsgType; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java index 22b525bab6..f21aa857bf 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicStatusCodeResponse.java @@ -16,26 +16,27 @@ package org.thingsboard.server.common.msg.core; import lombok.ToString; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; @ToString public class BasicStatusCodeResponse extends BasicResponseMsg implements StatusCodeResponse { private static final long serialVersionUID = 1L; - public static BasicStatusCodeResponse onSuccess(MsgType requestMsgType, Integer requestId) { + public static BasicStatusCodeResponse onSuccess(SessionMsgType requestMsgType, Integer requestId) { return BasicStatusCodeResponse.onSuccess(requestMsgType, requestId, 0); } - public static BasicStatusCodeResponse onSuccess(MsgType requestMsgType, Integer requestId, Integer code) { + public static BasicStatusCodeResponse onSuccess(SessionMsgType requestMsgType, Integer requestId, Integer code) { return new BasicStatusCodeResponse(requestMsgType, requestId, true, null, code); } - public static BasicStatusCodeResponse onError(MsgType requestMsgType, Integer requestId, Exception error) { + public static BasicStatusCodeResponse onError(SessionMsgType requestMsgType, Integer requestId, Exception error) { return new BasicStatusCodeResponse(requestMsgType, requestId, false, error, null); } - private BasicStatusCodeResponse(MsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { - super(requestMsgType, requestId, MsgType.STATUS_CODE_RESPONSE, success, error, code); + private BasicStatusCodeResponse(SessionMsgType requestMsgType, Integer requestId, boolean success, Exception error, Integer code) { + super(requestMsgType, requestId, SessionMsgType.STATUS_CODE_RESPONSE, success, error, code); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java index 1d96a65e29..60faeb1829 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/BasicTelemetryUploadRequest.java @@ -21,7 +21,8 @@ import java.util.List; import java.util.Map; import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; public class BasicTelemetryUploadRequest extends BasicRequest implements TelemetryUploadRequest { @@ -48,8 +49,8 @@ public class BasicTelemetryUploadRequest extends BasicRequest implements Telemet } @Override - public MsgType getMsgType() { - return MsgType.POST_TELEMETRY_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.POST_TELEMETRY_REQUEST; } @Override diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java index 2eb0959eec..3e70460dcb 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ResponseMsg.java @@ -18,12 +18,12 @@ package org.thingsboard.server.common.msg.core; import java.io.Serializable; import java.util.Optional; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; public interface ResponseMsg extends ToDeviceMsg { - MsgType getRequestMsgType(); + SessionMsgType getRequestMsgType(); Integer getRequestId(); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java index d4fc1d7e07..f8f24e8d20 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcSubscribeMsg.java @@ -16,14 +16,15 @@ package org.thingsboard.server.common.msg.core; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka */ public class RpcSubscribeMsg implements FromDeviceMsg { @Override - public MsgType getMsgType() { - return MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java index b1532d7b3b..23eb238d79 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RpcUnsubscribeMsg.java @@ -16,14 +16,15 @@ package org.thingsboard.server.common.msg.core; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka */ public class RpcUnsubscribeMsg implements FromDeviceMsg { @Override - public MsgType getMsgType() { - return MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java index bc8ceb4dbc..dcfde0fb52 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineError.java @@ -21,7 +21,7 @@ package org.thingsboard.server.common.msg.core; public enum RuleEngineError { - NO_RULES, NO_ACTIVE_RULES, NO_FILTERS_MATCHED, NO_REQUEST_FROM_ACTIONS, NO_TWO_WAY_ACTIONS, NO_RESPONSE_FROM_ACTIONS, PLUGIN_TIMEOUT(true); + QUEUE_PUT_TIMEOUT(true), SERVER_ERROR(true), TIMEOUT; private final boolean critical; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java index 5cb3314e1d..e0ff23b379 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/RuleEngineErrorMsg.java @@ -16,7 +16,8 @@ package org.thingsboard.server.common.msg.core; import lombok.Data; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; /** @@ -25,7 +26,7 @@ import org.thingsboard.server.common.msg.session.ToDeviceMsg; @Data public class RuleEngineErrorMsg implements ToDeviceMsg { - private final MsgType inMsgType; + private final SessionMsgType inSessionMsgType; private final RuleEngineError error; @Override @@ -33,27 +34,18 @@ public class RuleEngineErrorMsg implements ToDeviceMsg { return false; } - @Override - public MsgType getMsgType() { - return MsgType.RULE_ENGINE_ERROR; + public SessionMsgType getSessionMsgType() { + return SessionMsgType.RULE_ENGINE_ERROR; } public String getErrorMsg() { switch (error) { - case NO_RULES: - return "No rules configured!"; - case NO_ACTIVE_RULES: - return "No active rules!"; - case NO_FILTERS_MATCHED: - return "No rules that match current message!"; - case NO_REQUEST_FROM_ACTIONS: - return "Rule filters match, but no plugin message produced by rule action!"; - case NO_TWO_WAY_ACTIONS: - return "Rule filters match, but no rule with two-way action configured!"; - case NO_RESPONSE_FROM_ACTIONS: - return "Rule filters match, message processed by plugin, but no response produced by rule action!"; - case PLUGIN_TIMEOUT: - return "Timeout during processing of message by plugin!"; + case QUEUE_PUT_TIMEOUT: + return "Timeout during persistence of the message to the queue!"; + case SERVER_ERROR: + return "Error during processing of message by the server!"; + case TIMEOUT: + return "Timeout during processing of message by the server!"; default: throw new RuntimeException("Error " + error + " is not supported!"); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java index 738bc6e138..6d3ad5e8be 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseMsg.java @@ -16,14 +16,15 @@ package org.thingsboard.server.common.msg.core; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka */ public class SessionCloseMsg implements FromDeviceMsg { @Override - public MsgType getMsgType() { - return MsgType.SESSION_CLOSE; + public SessionMsgType getMsgType() { + return SessionMsgType.SESSION_CLOSE; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java index bf3c982922..bf36a9b3c3 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionCloseNotification.java @@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core; import lombok.ToString; import org.thingsboard.server.common.msg.kv.AttributesKVMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; @ToString @@ -30,9 +31,8 @@ public class SessionCloseNotification implements ToDeviceMsg { return true; } - @Override - public MsgType getMsgType() { - return MsgType.SESSION_CLOSE; + public SessionMsgType getSessionMsgType() { + return SessionMsgType.SESSION_CLOSE; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java index f3a7bc7bae..c121a71584 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/SessionOpenMsg.java @@ -16,14 +16,15 @@ package org.thingsboard.server.common.msg.core; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka */ public class SessionOpenMsg implements FromDeviceMsg { @Override - public MsgType getMsgType() { - return MsgType.SESSION_OPEN; + public SessionMsgType getMsgType() { + return SessionMsgType.SESSION_OPEN; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java index c9eeb4ef1a..d1a1f96451 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcRequestMsg.java @@ -16,7 +16,8 @@ package org.thingsboard.server.common.msg.core; import lombok.Data; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; /** @@ -29,9 +30,8 @@ public class ToDeviceRpcRequestMsg implements ToDeviceMsg { private final String method; private final String params; - @Override - public MsgType getMsgType() { - return MsgType.TO_DEVICE_RPC_REQUEST; + public SessionMsgType getSessionMsgType() { + return SessionMsgType.TO_DEVICE_RPC_REQUEST; } @Override diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java index ec739b379c..4fa3024947 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToDeviceRpcResponseMsg.java @@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core; import lombok.Data; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka @@ -29,7 +30,7 @@ public class ToDeviceRpcResponseMsg implements FromDeviceMsg { private final String data; @Override - public MsgType getMsgType() { - return MsgType.TO_DEVICE_RPC_RESPONSE; + public SessionMsgType getMsgType() { + return SessionMsgType.TO_DEVICE_RPC_RESPONSE; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java index 87708a759f..3823acac70 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcRequestMsg.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.msg.core; import lombok.Data; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; /** * @author Andrew Shvayka @@ -30,7 +30,7 @@ public class ToServerRpcRequestMsg implements FromDeviceRequestMsg { private final String params; @Override - public MsgType getMsgType() { - return MsgType.TO_SERVER_RPC_REQUEST; + public SessionMsgType getMsgType() { + return SessionMsgType.TO_SERVER_RPC_REQUEST; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java index b9a43d13a9..82f44e9113 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/core/ToServerRpcResponseMsg.java @@ -17,7 +17,8 @@ package org.thingsboard.server.common.msg.core; import lombok.Data; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; /** @@ -29,9 +30,8 @@ public class ToServerRpcResponseMsg implements ToDeviceMsg { private final int requestId; private final String data; - @Override - public MsgType getMsgType() { - return MsgType.TO_SERVER_RPC_RESPONSE; + public SessionMsgType getSessionMsgType() { + return SessionMsgType.TO_SERVER_RPC_RESPONSE; } @Override diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicToDeviceActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicDeviceToDeviceActorMsg.java similarity index 76% rename from common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicToDeviceActorMsg.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicDeviceToDeviceActorMsg.java index 959da7c4b3..1da19cb3cb 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicToDeviceActorMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/device/BasicDeviceToDeviceActorMsg.java @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.SessionId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.session.FromDeviceMsg; import org.thingsboard.server.common.msg.session.SessionType; @@ -28,7 +29,7 @@ import org.thingsboard.server.common.msg.session.ToDeviceActorSessionMsg; import java.util.Optional; @ToString -public class BasicToDeviceActorMsg implements ToDeviceActorMsg { +public class BasicDeviceToDeviceActorMsg implements DeviceToDeviceActorMsg { private static final long serialVersionUID = -1866795134993115408L; @@ -40,16 +41,16 @@ public class BasicToDeviceActorMsg implements ToDeviceActorMsg { private final ServerAddress serverAddress; private final FromDeviceMsg msg; - public BasicToDeviceActorMsg(ToDeviceActorMsg other, FromDeviceMsg msg) { + public BasicDeviceToDeviceActorMsg(DeviceToDeviceActorMsg other, FromDeviceMsg msg) { this(null, other.getTenantId(), other.getCustomerId(), other.getDeviceId(), other.getSessionId(), other.getSessionType(), msg); } - public BasicToDeviceActorMsg(ToDeviceActorSessionMsg msg, SessionType sessionType) { + public BasicDeviceToDeviceActorMsg(ToDeviceActorSessionMsg msg, SessionType sessionType) { this(null, msg.getTenantId(), msg.getCustomerId(), msg.getDeviceId(), msg.getSessionId(), sessionType, msg.getSessionMsg().getMsg()); } - private BasicToDeviceActorMsg(ServerAddress serverAddress, TenantId tenantId, CustomerId customerId, DeviceId deviceId, SessionId sessionId, SessionType sessionType, - FromDeviceMsg msg) { + private BasicDeviceToDeviceActorMsg(ServerAddress serverAddress, TenantId tenantId, CustomerId customerId, DeviceId deviceId, SessionId sessionId, SessionType sessionType, + FromDeviceMsg msg) { super(); this.serverAddress = serverAddress; this.tenantId = tenantId; @@ -95,7 +96,12 @@ public class BasicToDeviceActorMsg implements ToDeviceActorMsg { } @Override - public ToDeviceActorMsg toOtherAddress(ServerAddress otherAddress) { - return new BasicToDeviceActorMsg(otherAddress, tenantId, customerId, deviceId, sessionId, sessionType, msg); + public DeviceToDeviceActorMsg toOtherAddress(ServerAddress otherAddress) { + return new BasicDeviceToDeviceActorMsg(otherAddress, tenantId, customerId, deviceId, sessionId, sessionType, msg); + } + + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_SESSION_TO_DEVICE_ACTOR_MSG; } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/device/ToDeviceActorMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/device/DeviceToDeviceActorMsg.java similarity index 84% rename from common/message/src/main/java/org/thingsboard/server/common/msg/device/ToDeviceActorMsg.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/device/DeviceToDeviceActorMsg.java index 72e552d85b..fe8d9f2c21 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/device/ToDeviceActorMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/device/DeviceToDeviceActorMsg.java @@ -19,6 +19,7 @@ import java.io.Serializable; import java.util.Optional; import org.thingsboard.server.common.data.id.SessionId; +import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.CustomerAwareMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; @@ -26,7 +27,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.session.FromDeviceMsg; import org.thingsboard.server.common.msg.session.SessionType; -public interface ToDeviceActorMsg extends DeviceAwareMsg, CustomerAwareMsg, TenantAwareMsg, Serializable { +public interface DeviceToDeviceActorMsg extends TbActorMsg, DeviceAwareMsg, CustomerAwareMsg, TenantAwareMsg, Serializable { SessionId getSessionId(); @@ -36,5 +37,5 @@ public interface ToDeviceActorMsg extends DeviceAwareMsg, CustomerAwareMsg, Tena FromDeviceMsg getPayload(); - ToDeviceActorMsg toOtherAddress(ServerAddress otherAddress); + DeviceToDeviceActorMsg toOtherAddress(ServerAddress otherAddress); } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java similarity index 84% rename from extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java index a303b69445..c5cb6bd2f8 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/ToDeviceRpcRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.api.plugins.msg; +package org.thingsboard.server.common.msg.rpc; import lombok.Data; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.extensions.api.plugins.PluginApiCallSecurityContext; +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import java.io.Serializable; import java.util.UUID; @@ -29,7 +29,6 @@ import java.util.UUID; @Data public class ToDeviceRpcRequest implements Serializable { private final UUID id; - private final PluginApiCallSecurityContext securityCtx; private final TenantId tenantId; private final DeviceId deviceId; private final boolean oneway; diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java index 19d45a7095..71b605712f 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FromDeviceMsg.java @@ -19,6 +19,6 @@ import java.io.Serializable; public interface FromDeviceMsg extends Serializable { - MsgType getMsgType(); + SessionMsgType getMsgType(); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java similarity index 93% rename from common/message/src/main/java/org/thingsboard/server/common/msg/session/MsgType.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java index ddc5149d14..c7109c9703 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/MsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.msg.session; -public enum MsgType { +public enum SessionMsgType { GET_ATTRIBUTES_REQUEST(true), POST_ATTRIBUTES_REQUEST(true), GET_ATTRIBUTES_RESPONSE, SUBSCRIBE_ATTRIBUTES_REQUEST, UNSUBSCRIBE_ATTRIBUTES_REQUEST, ATTRIBUTES_UPDATE_NOTIFICATION, @@ -32,11 +32,11 @@ public enum MsgType { private final boolean requiresRulesProcessing; - MsgType() { + SessionMsgType() { this(false); } - MsgType(boolean requiresRulesProcessing) { + SessionMsgType(boolean requiresRulesProcessing) { this.requiresRulesProcessing = requiresRulesProcessing; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java index 31ec1fa7f9..705e86420b 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/ToDeviceMsg.java @@ -21,6 +21,6 @@ public interface ToDeviceMsg extends Serializable { boolean isSuccess(); - MsgType getMsgType(); + SessionMsgType getSessionMsgType(); } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutUUIDMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorClientSideRpcTimeoutMsg.java similarity index 64% rename from extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutUUIDMsg.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorClientSideRpcTimeoutMsg.java index d6a697e64e..f5cdfd1c26 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutUUIDMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorClientSideRpcTimeoutMsg.java @@ -13,17 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.api.plugins.msg; +package org.thingsboard.server.common.msg.timeout; -import java.util.UUID; +import org.thingsboard.server.common.msg.MsgType; /** * @author Andrew Shvayka */ -public final class TimeoutUUIDMsg extends TimeoutMsg { +public final class DeviceActorClientSideRpcTimeoutMsg extends TimeoutMsg { - public TimeoutUUIDMsg(UUID id, long timeout) { + public DeviceActorClientSideRpcTimeoutMsg(Integer id, long timeout) { super(id, timeout); } + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG; + } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorQueueTimeoutMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorQueueTimeoutMsg.java new file mode 100644 index 0000000000..0277983d8b --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorQueueTimeoutMsg.java @@ -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.common.msg.timeout; + +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.timeout.TimeoutMsg; + +import java.util.UUID; + +/** + * @author Andrew Shvayka + */ +public final class DeviceActorQueueTimeoutMsg extends TimeoutMsg { + + public DeviceActorQueueTimeoutMsg(UUID id, long timeout) { + super(id, timeout); + } + + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_ACTOR_QUEUE_TIMEOUT_MSG; + } +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorServerSideRpcTimeoutMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorServerSideRpcTimeoutMsg.java new file mode 100644 index 0000000000..cd86892b7f --- /dev/null +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/DeviceActorServerSideRpcTimeoutMsg.java @@ -0,0 +1,33 @@ +/** + * 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.common.msg.timeout; + +import org.thingsboard.server.common.msg.MsgType; + +/** + * @author Andrew Shvayka + */ +public final class DeviceActorServerSideRpcTimeoutMsg extends TimeoutMsg { + + public DeviceActorServerSideRpcTimeoutMsg(Integer id, long timeout) { + super(id, timeout); + } + + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_ACTOR_SERVER_SIDE_RPC_TIMEOUT_MSG; + } +} diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/TimeoutMsg.java similarity index 81% rename from extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutMsg.java rename to common/message/src/main/java/org/thingsboard/server/common/msg/timeout/TimeoutMsg.java index 03b2cd70ac..503188b006 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/timeout/TimeoutMsg.java @@ -13,15 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.api.plugins.msg; +package org.thingsboard.server.common.msg.timeout; import lombok.Data; +import org.thingsboard.server.common.msg.TbActorMsg; /** * @author Andrew Shvayka */ @Data -public class TimeoutMsg { +public abstract class TimeoutMsg implements TbActorMsg { private final T id; private final long timeout; } diff --git a/common/message/src/main/proto/tbmsg.proto b/common/message/src/main/proto/tbmsg.proto index 4ce1fb69bc..60003dc420 100644 --- a/common/message/src/main/proto/tbmsg.proto +++ b/common/message/src/main/proto/tbmsg.proto @@ -27,10 +27,19 @@ message TbMsgProto { string id = 1; string type = 2; string entityType = 3; - string entityId = 4; + int64 entityIdMSB = 4; + int64 entityIdLSB = 5; - TbMsgMetaDataProto metaData = 5; + int64 ruleChainIdMSB = 6; + int64 ruleChainIdLSB = 7; + + int64 ruleNodeIdMSB = 8; + int64 ruleNodeIdLSB = 9; + int64 clusterPartition = 10; + + TbMsgMetaDataProto metaData = 11; + + int32 dataType = 12; + string data = 13; - int32 dataType = 6; - string data = 7; } \ No newline at end of file diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java index 93a6e9965f..080f874efb 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/TransportAdaptor.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.transport; import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; import org.thingsboard.server.common.msg.session.SessionContext; import org.thingsboard.server.common.transport.adaptor.AdaptorException; @@ -25,7 +25,7 @@ import java.util.Optional; public interface TransportAdaptor { - AdaptorToSessionActorMsg convertToActorMsg(C ctx, MsgType type, T inbound) throws AdaptorException; + AdaptorToSessionActorMsg convertToActorMsg(C ctx, SessionMsgType type, T inbound) throws AdaptorException; Optional convertToAdaptorMsg(C ctx, SessionActorToAdaptorMsg msg) throws AdaptorException; diff --git a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 390266a614..b99c450326 100644 --- a/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -118,13 +118,13 @@ public class JsonConverter { } } - public static UpdateAttributesRequest convertToAttributes(JsonElement element) { + public static AttributesUpdateRequest convertToAttributes(JsonElement element) { return convertToAttributes(element, BasicRequest.DEFAULT_REQUEST_ID); } - public static UpdateAttributesRequest convertToAttributes(JsonElement element, int requestId) { + public static AttributesUpdateRequest convertToAttributes(JsonElement element, int requestId) { if (element.isJsonObject()) { - BasicUpdateAttributesRequest request = new BasicUpdateAttributesRequest(requestId); + BasicAttributesUpdateRequest request = new BasicAttributesUpdateRequest(requestId); long ts = System.currentTimeMillis(); request.add(parseValues(element.getAsJsonObject()).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); return request; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 52b15efebd..37b9158213 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -352,6 +352,7 @@ public class ModelConstants { * Cassandra rule node constants. */ public static final String RULE_NODE_COLUMN_FAMILY_NAME = "rule_node"; + public static final String RULE_NODE_CHAIN_ID_PROPERTY = "rule_chain_id"; public static final String RULE_NODE_TYPE_PROPERTY = "type"; public static final String RULE_NODE_NAME_PROPERTY = "name"; public static final String RULE_NODE_CONFIGURATION_PROPERTY = "configuration"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java index 8d3f3c3eaf..ec13022600 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/nosql/RuleNodeEntity.java @@ -24,6 +24,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.model.SearchTextEntity; @@ -41,6 +42,8 @@ public class RuleNodeEntity implements SearchTextEntity { @PartitionKey @Column(name = ID_PROPERTY) private UUID id; + @Column(name = RULE_NODE_CHAIN_ID_PROPERTY) + private UUID ruleChainId; @Column(name = RULE_NODE_TYPE_PROPERTY) private String type; @Column(name = RULE_NODE_NAME_PROPERTY) @@ -56,7 +59,6 @@ public class RuleNodeEntity implements SearchTextEntity { @Column(name = DEBUG_MODE) private boolean debugMode; - public RuleNodeEntity() { } @@ -64,6 +66,9 @@ public class RuleNodeEntity implements SearchTextEntity { if (ruleNode.getId() != null) { this.id = ruleNode.getUuidId(); } + if (ruleNode.getRuleChainId() != null) { + this.ruleChainId = ruleNode.getRuleChainId().getId(); + } this.type = ruleNode.getType(); this.name = ruleNode.getName(); this.debugMode = ruleNode.isDebugMode(); @@ -92,6 +97,14 @@ public class RuleNodeEntity implements SearchTextEntity { this.id = id; } + public UUID getRuleChainId() { + return ruleChainId; + } + + public void setRuleChainId(UUID ruleChainId) { + this.ruleChainId = ruleChainId; + } + public String getType() { return type; } @@ -132,6 +145,9 @@ public class RuleNodeEntity implements SearchTextEntity { public RuleNode toData() { RuleNode ruleNode = new RuleNode(new RuleNodeId(id)); ruleNode.setCreatedTime(UUIDs.unixTimestamp(id)); + if (this.ruleChainId != null) { + ruleNode.setRuleChainId(new RuleChainId(this.ruleChainId)); + } ruleNode.setType(this.type); ruleNode.setName(this.name); ruleNode.setDebugMode(this.debugMode); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java index 6a888c2d72..1d0543341f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RuleNodeEntity.java @@ -21,8 +21,10 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; @@ -39,6 +41,9 @@ import javax.persistence.Table; @Table(name = ModelConstants.RULE_NODE_COLUMN_FAMILY_NAME) public class RuleNodeEntity extends BaseSqlEntity implements SearchTextEntity { + @Column(name = ModelConstants.RULE_NODE_CHAIN_ID_PROPERTY) + private String ruleChainId; + @Column(name = ModelConstants.RULE_NODE_TYPE_PROPERTY) private String type; @@ -66,6 +71,9 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex if (ruleNode.getId() != null) { this.setId(ruleNode.getUuidId()); } + if (ruleNode.getRuleChainId() != null) { + this.ruleChainId = toString(DaoUtil.getId(ruleNode.getRuleChainId())); + } this.type = ruleNode.getType(); this.name = ruleNode.getName(); this.debugMode = ruleNode.isDebugMode(); @@ -88,6 +96,9 @@ public class RuleNodeEntity extends BaseSqlEntity implements SearchTex public RuleNode toData() { RuleNode ruleNode = new RuleNode(new RuleNodeId(getId())); ruleNode.setCreatedTime(UUIDs.unixTimestamp(getId())); + if (ruleChainId != null) { + ruleNode.setRuleChainId(new RuleChainId(toUUID(ruleChainId))); + } ruleNode.setType(type); ruleNode.setName(name); ruleNode.setDebugMode(debugMode); diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/MsqQueue.java b/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java similarity index 97% rename from dao/src/main/java/org/thingsboard/server/dao/queue/MsqQueue.java rename to dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java index 345012892c..e49b89eb81 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/MsqQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/MsgQueue.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.msg.TbMsg; import java.util.UUID; -public interface MsqQueue { +public interface MsgQueue { ListenableFuture put(TbMsg msg, UUID nodeId, long clusterPartition); diff --git a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java index 85f42ae169..ef55bcb1de 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java +++ b/dao/src/main/java/org/thingsboard/server/dao/queue/QueueBenchmark.java @@ -27,10 +27,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.ComponentScan; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -61,11 +60,11 @@ public class QueueBenchmark implements CommandLineRunner { } @Autowired - private MsqQueue msqQueue; + private MsgQueue msgQueue; @Override public void run(String... strings) throws Exception { - System.out.println("It works + " + msqQueue); + System.out.println("It works + " + msgQueue); long start = System.currentTimeMillis(); @@ -81,8 +80,8 @@ public class QueueBenchmark implements CommandLineRunner { try { TbMsg msg = randomMsg(); UUID nodeId = UUIDs.timeBased(); - ListenableFuture put = msqQueue.put(msg, nodeId, 100L); -// ListenableFuture put = msqQueue.ack(msg, nodeId, 100L); + ListenableFuture put = msgQueue.put(msg, nodeId, 100L); +// ListenableFuture put = msgQueue.ack(msg, nodeId, 100L); Futures.addCallback(put, new FutureCallback() { @Override public void onSuccess(@Nullable Void result) { @@ -126,7 +125,7 @@ public class QueueBenchmark implements CommandLineRunner { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("key", "value"); String dataStr = "someContent"; - return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr); + return new TbMsg(UUIDs.timeBased(), "type", null, metaData, TbMsgDataType.JSON, dataStr, new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L); } @Bean diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index cdb9a80991..9ce1fbebbc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.thingsboard.server.dao.rule; import com.google.common.util.concurrent.ListenableFuture; @@ -124,6 +123,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } } for (RuleNode node : toAddOrUpdate) { + node.setRuleChainId(ruleChain.getId()); RuleNode savedNode = ruleNodeDao.save(node); try { createRelation(new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), @@ -137,7 +137,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC nodes.set(index, savedNode); ruleNodeIndexMap.put(savedNode.getId(), index); } - for (RuleNode node: toDelete) { + for (RuleNode node : toDelete) { deleteRuleNode(node.getId()); } RuleNodeId firstRuleNodeId = null; @@ -233,6 +233,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return ruleChainDao.findByIdAsync(ruleChainId.getId()); } + @Override + public ListenableFuture findRuleNodeByIdAsync(RuleNodeId ruleNodeId) { + Validator.validateId(ruleNodeId, "Incorrect rule node id for search request."); + return ruleNodeDao.findByIdAsync(ruleNodeId.getId()); + } + @Override public RuleChain getRootTenantRuleChain(TenantId tenantId) { Validator.validateId(tenantId, "Incorrect tenant id for search request."); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index da7833d696..d516e54be4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -46,6 +46,8 @@ public interface RuleChainService { ListenableFuture findRuleChainByIdAsync(RuleChainId ruleChainId); + ListenableFuture findRuleNodeByIdAsync(RuleNodeId ruleNodeId); + RuleChain getRootTenantRuleChain(TenantId tenantId); List getRuleChainNodes(RuleChainId ruleChainId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsqQueue.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsgQueue.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsqQueue.java rename to dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsgQueue.java index 59e10d8f4c..361b18ce4b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsqQueue.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/CassandraMsgQueue.java @@ -22,7 +22,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.dao.queue.MsqQueue; +import org.thingsboard.server.dao.queue.MsgQueue; import org.thingsboard.server.dao.service.queue.cassandra.repository.AckRepository; import org.thingsboard.server.dao.service.queue.cassandra.repository.MsgRepository; import org.thingsboard.server.dao.util.NoSqlDao; @@ -33,7 +33,7 @@ import java.util.UUID; @Component @Slf4j @NoSqlDao -public class CassandraMsqQueue implements MsqQueue { +public class CassandraMsgQueue implements MsgQueue { @Autowired private MsgRepository msgRepository; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java index faad701d4c..a60f685f29 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitioner.java @@ -30,6 +30,7 @@ import java.time.ZoneOffset; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.TimeUnit; @Component @Slf4j @@ -60,7 +61,7 @@ public class QueuePartitioner { public List findUnprocessedPartitions(UUID nodeId, long clusteredHash) { Optional lastPartitionOption = processedPartitionRepository.findLastProcessedPartition(nodeId, clusteredHash); - long lastPartition = lastPartitionOption.orElse(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 100); + long lastPartition = lastPartitionOption.orElse(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)); List unprocessedPartitions = Lists.newArrayList(); LocalDateTime current = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastPartition), ZoneOffset.UTC); diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPluginConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgKey.java similarity index 76% rename from extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPluginConfiguration.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgKey.java index f4c52b9104..2090edf381 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPluginConfiguration.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgKey.java @@ -13,14 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.core.plugin.rpc; +package org.thingsboard.server.dao.sql.queue; import lombok.Data; +import java.util.UUID; + /** - * @author Andrew Shvayka + * Created by ashvayka on 30.04.18. */ @Data -public class RpcPluginConfiguration { - private long defaultTimeout; +public final class InMemoryMsgKey { + final UUID nodeId; + final long clusterPartition; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java new file mode 100644 index 0000000000..ce579cd00c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/queue/InMemoryMsgQueue.java @@ -0,0 +1,120 @@ +/** + * 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.dao.sql.queue; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.dao.queue.MsgQueue; +import org.thingsboard.server.dao.util.SqlDao; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicLong; + +/** + * Created by ashvayka on 27.04.18. + */ +@Component +//@ConditionalOnProperty(prefix = "rule.queue", value = "type", havingValue = "memory", matchIfMissing = true) +@Slf4j +@SqlDao +public class InMemoryMsgQueue implements MsgQueue { + + @Value("${rule.queue.max_size}") + @Getter + private long maxSize; + + private ListeningExecutorService queueExecutor; + private AtomicLong pendingMsgCount = new AtomicLong(); + private Map> data = new HashMap<>(); + + @PostConstruct + public void init() { + // Should be always single threaded due to absence of locks. + queueExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + } + + @PreDestroy + public void stop() { + if (queueExecutor == null) { + queueExecutor.shutdownNow(); + } + } + + @Override + public ListenableFuture put(TbMsg msg, UUID nodeId, long clusterPartition) { + if (pendingMsgCount.get() < maxSize) { + return queueExecutor.submit(() -> { + data.computeIfAbsent(new InMemoryMsgKey(nodeId, clusterPartition), key -> new HashMap<>()).put(msg.getId(), msg); + pendingMsgCount.incrementAndGet(); + return null; + }); + } else { + return Futures.immediateFailedFuture(new RuntimeException("Message queue is full!")); + } + } + + @Override + public ListenableFuture ack(TbMsg msg, UUID nodeId, long clusterPartition) { + return queueExecutor.submit(() -> { + InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); + Map map = data.get(key); + if (map != null) { + if (map.remove(msg.getId()) != null) { + pendingMsgCount.decrementAndGet(); + } + if (map.isEmpty()) { + data.remove(key); + } + } + return null; + }); + } + + @Override + public Iterable findUnprocessed(UUID nodeId, long clusterPartition) { + ListenableFuture> list = queueExecutor.submit(() -> { + InMemoryMsgKey key = new InMemoryMsgKey(nodeId, clusterPartition); + Map map = data.get(key); + if (map != null) { + return new ArrayList<>(map.values()); + } else { + return Collections.emptyList(); + } + }); + try { + return list.get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } +} diff --git a/dao/src/main/resources/cassandra/schema.cql b/dao/src/main/resources/cassandra/schema.cql index 79e96554ec..41e040613d 100644 --- a/dao/src/main/resources/cassandra/schema.cql +++ b/dao/src/main/resources/cassandra/schema.cql @@ -686,6 +686,7 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.rule_chain_by_tenant_and_sear CREATE TABLE IF NOT EXISTS thingsboard.rule_node ( id uuid, + rule_chain_id uuid, type text, name text, debug_mode boolean, diff --git a/dao/src/main/resources/sql/schema.sql b/dao/src/main/resources/sql/schema.sql index 08fe7fa529..f2458e1613 100644 --- a/dao/src/main/resources/sql/schema.sql +++ b/dao/src/main/resources/sql/schema.sql @@ -272,6 +272,7 @@ CREATE TABLE IF NOT EXISTS rule_chain ( 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), diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java index a6ec464e6b..2d3b61fec1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/QueuePartitionerTest.java @@ -75,7 +75,7 @@ public class QueuePartitionerTest { long clusteredHash = 101L; when(partitionRepo.findLastProcessedPartition(nodeId, clusteredHash)).thenReturn(Optional.empty()); List actual = queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash); - assertEquals(1011, actual.size()); + assertEquals(10083, actual.size()); } } \ No newline at end of file diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java index 3935c9b92e..caccc85932 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/UnprocessedMsgFilterTest.java @@ -33,8 +33,8 @@ public class UnprocessedMsgFilterTest { public void acknowledgedMsgsAreFilteredOut() { UUID id1 = UUID.randomUUID(); UUID id2 = UUID.randomUUID(); - TbMsg msg1 = new TbMsg(id1, "T", null, null, null, null); - TbMsg msg2 = new TbMsg(id2, "T", null, null, null, null); + TbMsg msg1 = new TbMsg(id1, "T", null, null, null, null, null, null, 0L); + TbMsg msg2 = new TbMsg(id2, "T", null, null, null, null, null, null, 0L); List msgs = Lists.newArrayList(msg1, msg2); List acks = Lists.newArrayList(new MsgAck(id2, UUID.randomUUID(), 1L, 1L)); Collection actual = msgFilter.filter(msgs, acks); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java index a86238e3b1..cff4dc9d0f 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraAckRepositoryTest.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; import org.thingsboard.server.dao.service.queue.cassandra.MsgAck; @@ -66,6 +67,7 @@ public class CassandraAckRepositoryTest extends AbstractServiceTest { @Test public void expiredAcksAreNotReturned() throws ExecutionException, InterruptedException { + ReflectionTestUtils.setField(ackRepository, "ackQueueTtl", 1); UUID msgId = UUIDs.timeBased(); UUID nodeId = UUIDs.timeBased(); MsgAck ack = new MsgAck(msgId, nodeId, 30L, 40L); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java index a766aa48ce..fa286aaea2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraMsgRepositoryTest.java @@ -22,7 +22,10 @@ import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -45,7 +48,8 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { @Test public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException { - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000", + new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L); UUID nodeId = UUIDs.timeBased(); ListenableFuture future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); future.get(); @@ -55,7 +59,9 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { @Test public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException { - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000"); + ReflectionTestUtils.setField(msgRepository, "msqQueueTtl", 1); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, TbMsgDataType.JSON, "0000", + new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L); UUID nodeId = UUIDs.timeBased(); ListenableFuture future = msgRepository.save(msg, nodeId, 2L, 2L, 2L); future.get(); @@ -68,7 +74,8 @@ public class CassandraMsgRepositoryTest extends AbstractServiceTest { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("key", "value"); String dataStr = "someContent"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, TbMsgDataType.JSON, dataStr, + new RuleChainId(UUIDs.timeBased()), new RuleNodeId(UUIDs.timeBased()), 0L); UUID nodeId = UUIDs.timeBased(); ListenableFuture future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); future.get(); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java index 1b487903b9..2ae810a221 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/queue/cassandra/repository/impl/CassandraProcessedPartitionRepositoryTest.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.util.ReflectionTestUtils; import org.thingsboard.server.dao.service.AbstractServiceTest; import org.thingsboard.server.dao.service.DaoNoSqlTest; @@ -61,6 +62,7 @@ public class CassandraProcessedPartitionRepositoryTest extends AbstractServiceTe @Test public void expiredPartitionsAreNotReturned() throws ExecutionException, InterruptedException { + ReflectionTestUtils.setField(partitionRepository, "partitionsTtl", 1); UUID nodeId = UUIDs.timeBased(); ListenableFuture future = partitionRepository.partitionProcessed(nodeId, 404L, 10L); future.get(); diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index 42b71f84e3..dbd8b84a4b 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -28,3 +28,6 @@ redis.connection.host=localhost redis.connection.port=6379 redis.connection.db=0 redis.connection.password= + +rule.queue.type=memory +rule.queue.max_size=10000 \ No newline at end of file diff --git a/dao/src/test/resources/nosql-test.properties b/dao/src/test/resources/nosql-test.properties index 7c02666675..e37e228b40 100644 --- a/dao/src/test/resources/nosql-test.properties +++ b/dao/src/test/resources/nosql-test.properties @@ -1,6 +1,6 @@ database.type=cassandra cassandra.queue.partitioning=HOURS -cassandra.queue.ack.ttl=1 -cassandra.queue.msg.ttl=1 -cassandra.queue.partitions.ttl=1 \ No newline at end of file +cassandra.queue.ack.ttl=3600 +cassandra.queue.msg.ttl=3600 +cassandra.queue.partitions.ttl=3600 \ No newline at end of file diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java index 2fcf559852..a470784b32 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceAttributesEventNotificationMsg.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.msg.MsgType; import java.util.List; import java.util.Set; @@ -54,4 +55,8 @@ public class DeviceAttributesEventNotificationMsg implements ToDeviceActorNotifi return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keys, null, null, true); } + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_ATTRIBUTES_UPDATE_TO_DEVICE_ACTOR_MSG; + } } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java index 937018e655..b634a8298f 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceCredentialsUpdateNotificationMsg.java @@ -21,6 +21,7 @@ import lombok.ToString; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; +import org.thingsboard.server.common.msg.MsgType; import java.util.Set; @@ -33,4 +34,8 @@ public class DeviceCredentialsUpdateNotificationMsg implements ToDeviceActorNoti private final TenantId tenantId; private final DeviceId deviceId; + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_CREDENTIALS_UPDATE_TO_DEVICE_ACTOR_MSG; + } } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java index 908319082f..1823e3b335 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/DeviceNameOrTypeUpdateMsg.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Data; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.MsgType; @Data @AllArgsConstructor @@ -27,4 +28,9 @@ public class DeviceNameOrTypeUpdateMsg implements ToDeviceActorNotificationMsg { private final DeviceId deviceId; private final String deviceName; private final String deviceType; + + @Override + public MsgType getMsgType() { + return MsgType.DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG; + } } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java index 892d53eee9..5cd5d82946 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/device/ToDeviceActorNotificationMsg.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.extensions.api.device; +import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.DeviceAwareMsg; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; @@ -23,6 +24,6 @@ import java.io.Serializable; /** * @author Andrew Shvayka */ -public interface ToDeviceActorNotificationMsg extends TenantAwareMsg, DeviceAwareMsg, Serializable { +public interface ToDeviceActorNotificationMsg extends TbActorMsg, TenantAwareMsg, DeviceAwareMsg, Serializable { } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java index 34f4ce794a..6d54f3edb3 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/AbstractPlugin.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.extensions.api.plugins.handlers.*; import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.common.msg.timeout.TimeoutMsg; import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java index a41f3c14e2..20d74c3ba4 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/Plugin.java @@ -21,7 +21,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.extensions.api.component.ConfigurableComponent; import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; +import org.thingsboard.server.common.msg.timeout.TimeoutMsg; import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; import org.thingsboard.server.extensions.api.plugins.ws.msg.PluginWebsocketMsg; diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java index 3c19571560..69bee5ca97 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginAction.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.extensions.api.plugins; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.ToDeviceMsg; import org.thingsboard.server.extensions.api.component.ConfigurableComponent; import org.thingsboard.server.extensions.api.plugins.msg.PluginToRuleMsg; @@ -28,7 +28,7 @@ import java.util.Optional; public interface PluginAction extends ConfigurableComponent, RuleLifecycleComponent { - Optional convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd); + Optional convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData deviceMsgMd); Optional convert(PluginToRuleMsg response); diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java index 37f4c4a383..42fd747155 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/PluginContext.java @@ -22,8 +22,10 @@ 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.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.msg.cluster.ServerAddress; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.common.msg.timeout.TimeoutMsg; import org.thingsboard.server.extensions.api.plugins.msg.*; import org.thingsboard.server.extensions.api.plugins.rpc.RpcMsg; import org.thingsboard.server.extensions.api.plugins.ws.PluginWebsocketSessionRef; diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java index 27b476d0c0..42dbbf9052 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/handlers/DefaultRuleMsgHandler.java @@ -18,7 +18,8 @@ package org.thingsboard.server.extensions.api.plugins.handlers; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.RuleId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.extensions.api.plugins.PluginContext; import org.thingsboard.server.extensions.api.plugins.msg.GetAttributesRequestRuleToPluginMsg; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; @@ -56,8 +57,8 @@ public class DefaultRuleMsgHandler implements RuleMsgHandler { msgTypeNotSupported(msg.getPayload().getMsgType()); } - private void msgTypeNotSupported(MsgType msgType) { - throw new RuntimeException("Not supported msg type: " + msgType + "!"); + private void msgTypeNotSupported(SessionMsgType sessionMsgType) { + throw new RuntimeException("Not supported msg type: " + sessionMsgType + "!"); } } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java index 991ad8d02d..a6d40ff77c 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/UpdateAttributesRequestRuleToPluginMsg.java @@ -18,13 +18,13 @@ package org.thingsboard.server.extensions.api.plugins.msg; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.core.UpdateAttributesRequest; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; -public class UpdateAttributesRequestRuleToPluginMsg extends AbstractRuleToPluginMsg { +public class UpdateAttributesRequestRuleToPluginMsg extends AbstractRuleToPluginMsg { private static final long serialVersionUID = 1L; - public UpdateAttributesRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, UpdateAttributesRequest payload) { + public UpdateAttributesRequestRuleToPluginMsg(TenantId tenantId, CustomerId customerId, DeviceId deviceId, AttributesUpdateRequest payload) { super(tenantId, customerId, deviceId, payload); } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java index 9489a539ca..f0e986ec13 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleFilter.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.extensions.api.rules; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.component.ConfigurableComponent; /** @@ -23,6 +23,6 @@ import org.thingsboard.server.extensions.api.component.ConfigurableComponent; */ public interface RuleFilter extends ConfigurableComponent, RuleLifecycleComponent { - boolean filter(RuleContext ctx, ToDeviceActorMsg msg); + boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg); } diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java index 3fd5e96318..5e72dbe445 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java +++ b/extensions-api/src/main/java/org/thingsboard/server/extensions/api/rules/RuleProcessor.java @@ -15,15 +15,13 @@ */ package org.thingsboard.server.extensions.api.rules; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.component.ConfigurableComponent; -import javax.script.ScriptException; - /** * @author Andrew Shvayka */ public interface RuleProcessor extends ConfigurableComponent, RuleLifecycleComponent { - RuleProcessingMetaData process(RuleContext ctx, ToDeviceActorMsg msg) throws RuleException; + RuleProcessingMetaData process(RuleContext ctx, DeviceToDeviceActorMsg msg) throws RuleException; } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java index f765a121e1..0258cf161c 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/mail/SendMailAction.java @@ -20,7 +20,7 @@ import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.runtime.parser.ParseException; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.ToDeviceMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.PluginAction; @@ -74,7 +74,7 @@ public class SendMailAction extends SimpleRuleLifecycleComponent implements Plug } @Override - public Optional convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData metadata) { + public Optional convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData metadata) { String sendFlag = configuration.getSendFlag(); if (StringUtils.isEmpty(sendFlag) || (Boolean) metadata.get(sendFlag).orElse(Boolean.FALSE)) { VelocityContext context = VelocityUtils.createContext(metadata); @@ -86,7 +86,7 @@ public class SendMailAction extends SimpleRuleLifecycleComponent implements Plug bccTemplate.ifPresent(t -> builder.bcc(VelocityUtils.merge(t, context))); subjectTemplate.ifPresent(t -> builder.subject(VelocityUtils.merge(t, context))); bodyTemplate.ifPresent(t -> builder.body(VelocityUtils.merge(t, context))); - return Optional.of(new SendMailRuleToPluginActionMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), toDeviceActorMsg.getDeviceId(), + return Optional.of(new SendMailRuleToPluginActionMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), deviceToDeviceActorMsg.getDeviceId(), builder.build())); } else { return Optional.empty(); diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java index b1959822b5..8bd9e25e4e 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/RpcPluginAction.java @@ -16,9 +16,10 @@ package org.thingsboard.server.extensions.core.action.rpc; import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.component.EmptyComponentConfiguration; @@ -41,12 +42,12 @@ public class RpcPluginAction extends SimpleRuleLifecycleComponent implements Plu } @Override - public Optional convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) { - FromDeviceMsg msg = toDeviceActorMsg.getPayload(); - if (msg.getMsgType() == MsgType.TO_SERVER_RPC_REQUEST) { + public Optional convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) { + FromDeviceMsg msg = deviceToDeviceActorMsg.getPayload(); + if (msg.getMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) { ToServerRpcRequestMsg payload = (ToServerRpcRequestMsg) msg; - return Optional.of(new RpcRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), - toDeviceActorMsg.getDeviceId(), payload)); + return Optional.of(new RpcRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), + deviceToDeviceActorMsg.getDeviceId(), payload)); } else { return Optional.empty(); } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java index 80c8efba68..4e129dbf0a 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/rpc/ServerSideRpcCallAction.java @@ -20,7 +20,7 @@ import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.runtime.parser.ParseException; import org.springframework.util.StringUtils; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.ToDeviceMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.PluginAction; @@ -64,7 +64,7 @@ public class ServerSideRpcCallAction extends SimpleRuleLifecycleComponent implem } @Override - public Optional convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData metadata) { + public Optional convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData metadata) { String sendFlag = configuration.getSendFlag(); if (StringUtils.isEmpty(sendFlag) || (Boolean) metadata.get(sendFlag).orElse(Boolean.FALSE)) { VelocityContext context = VelocityUtils.createContext(metadata); @@ -77,7 +77,7 @@ public class ServerSideRpcCallAction extends SimpleRuleLifecycleComponent implem rpcCallMethodTemplate.ifPresent(t -> builder.rpcCallMethod(VelocityUtils.merge(t, context))); rpcCallBodyTemplate.ifPresent(t -> builder.rpcCallBody(VelocityUtils.merge(t, context))); builder.rpcCallTimeoutInSec(configuration.getRpcCallTimeoutInSec()); - return Optional.of(new ServerSideRpcCallRuleToPluginActionMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), toDeviceActorMsg.getDeviceId(), + return Optional.of(new ServerSideRpcCallRuleToPluginActionMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), deviceToDeviceActorMsg.getDeviceId(), builder.build())); } else { return Optional.empty(); diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java index b36aced45e..b32137e011 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/telemetry/TelemetryPluginAction.java @@ -18,10 +18,10 @@ package org.thingsboard.server.extensions.core.action.telemetry; import org.springframework.util.StringUtils; import org.thingsboard.server.common.msg.core.GetAttributesRequest; import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; -import org.thingsboard.server.common.msg.core.UpdateAttributesRequest; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.ToDeviceMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.PluginAction; @@ -50,20 +50,20 @@ public class TelemetryPluginAction extends SimpleRuleLifecycleComponent implemen } @Override - public Optional convert(RuleContext ctx, ToDeviceActorMsg toDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) { - FromDeviceMsg msg = toDeviceActorMsg.getPayload(); - if (msg.getMsgType() == MsgType.POST_TELEMETRY_REQUEST) { + public Optional convert(RuleContext ctx, DeviceToDeviceActorMsg deviceToDeviceActorMsg, RuleProcessingMetaData deviceMsgMd) { + FromDeviceMsg msg = deviceToDeviceActorMsg.getPayload(); + if (msg.getMsgType() == SessionMsgType.POST_TELEMETRY_REQUEST) { TelemetryUploadRequest payload = (TelemetryUploadRequest) msg; - return Optional.of(new TelemetryUploadRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), - toDeviceActorMsg.getDeviceId(), payload, ttl)); - } else if (msg.getMsgType() == MsgType.POST_ATTRIBUTES_REQUEST) { - UpdateAttributesRequest payload = (UpdateAttributesRequest) msg; - return Optional.of(new UpdateAttributesRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), - toDeviceActorMsg.getDeviceId(), payload)); - } else if (msg.getMsgType() == MsgType.GET_ATTRIBUTES_REQUEST) { + return Optional.of(new TelemetryUploadRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), + deviceToDeviceActorMsg.getDeviceId(), payload, ttl)); + } else if (msg.getMsgType() == SessionMsgType.POST_ATTRIBUTES_REQUEST) { + AttributesUpdateRequest payload = (AttributesUpdateRequest) msg; + return Optional.of(new UpdateAttributesRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), + deviceToDeviceActorMsg.getDeviceId(), payload)); + } else if (msg.getMsgType() == SessionMsgType.GET_ATTRIBUTES_REQUEST) { GetAttributesRequest payload = (GetAttributesRequest) msg; - return Optional.of(new GetAttributesRequestRuleToPluginMsg(toDeviceActorMsg.getTenantId(), toDeviceActorMsg.getCustomerId(), - toDeviceActorMsg.getDeviceId(), payload)); + return Optional.of(new GetAttributesRequestRuleToPluginMsg(deviceToDeviceActorMsg.getTenantId(), deviceToDeviceActorMsg.getCustomerId(), + deviceToDeviceActorMsg.getDeviceId(), payload)); } else { return Optional.empty(); } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java index 6d7f75a031..82efdc51eb 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/action/template/AbstractTemplatePluginAction.java @@ -19,7 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.velocity.Template; import org.apache.velocity.VelocityContext; import org.apache.velocity.runtime.parser.ParseException; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.common.msg.session.ToDeviceMsg; import org.thingsboard.server.extensions.api.plugins.PluginAction; @@ -52,7 +52,7 @@ public abstract class AbstractTemplatePluginAction convert(RuleContext ctx, ToDeviceActorMsg msg, RuleProcessingMetaData deviceMsgMd) { + public Optional convert(RuleContext ctx, DeviceToDeviceActorMsg msg, RuleProcessingMetaData deviceMsgMd) { FromDeviceRequestMsg payload; if (msg.getPayload() instanceof FromDeviceRequestMsg) { payload = (FromDeviceRequestMsg) msg.getPayload(); @@ -70,14 +70,14 @@ public abstract class AbstractTemplatePluginAction buildRuleToPluginMsg(RuleContext ctx, - ToDeviceActorMsg msg, + DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload); @Override diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java index 9c092dae6e..469c76ae57 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/BasicJsFilter.java @@ -16,8 +16,7 @@ package org.thingsboard.server.extensions.core.filter; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.rules.RuleContext; import org.thingsboard.server.extensions.api.rules.RuleFilter; @@ -39,7 +38,7 @@ public abstract class BasicJsFilter implements RuleFilter } @Override - public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) { + public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) { try { return doFilter(ctx, msg); } catch (ScriptException e) { @@ -48,7 +47,7 @@ public abstract class BasicJsFilter implements RuleFilter } } - protected abstract boolean doFilter(RuleContext ctx, ToDeviceActorMsg msg) throws ScriptException; + protected abstract boolean doFilter(RuleContext ctx, DeviceToDeviceActorMsg msg) throws ScriptException; @Override public void resume() { diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java index db7f8c25d0..5865c1d219 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceAttributesFilter.java @@ -16,8 +16,8 @@ package org.thingsboard.server.extensions.core.filter; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.msg.core.UpdateAttributesRequest; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceMsg; import org.thingsboard.server.extensions.api.component.Filter; import org.thingsboard.server.extensions.api.device.DeviceAttributes; @@ -34,7 +34,7 @@ import javax.script.ScriptException; public class DeviceAttributesFilter extends BasicJsFilter { @Override - protected boolean doFilter(RuleContext ctx, ToDeviceActorMsg msg) throws ScriptException { + protected boolean doFilter(RuleContext ctx, DeviceToDeviceActorMsg msg) throws ScriptException { return evaluator.execute(toBindings(ctx.getDeviceMetaData().getDeviceAttributes(), msg != null ? msg.getPayload() : null)); } @@ -44,7 +44,7 @@ public class DeviceAttributesFilter extends BasicJsFilter { if (msg != null) { switch (msg.getMsgType()) { case POST_ATTRIBUTES_REQUEST: - bindings = NashornJsEvaluator.updateBindings(bindings, (UpdateAttributesRequest) msg); + bindings = NashornJsEvaluator.updateBindings(bindings, (AttributesUpdateRequest) msg); break; default: break; diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java index cd8f87c2b6..7c2ede54d4 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTelemetryFilter.java @@ -18,7 +18,7 @@ package org.thingsboard.server.extensions.core.filter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceMsg; import org.thingsboard.server.extensions.api.component.Filter; import org.thingsboard.server.extensions.api.rules.RuleContext; @@ -34,7 +34,7 @@ import java.util.List; public class DeviceTelemetryFilter extends BasicJsFilter { @Override - protected boolean doFilter(RuleContext ctx, ToDeviceActorMsg msg) throws ScriptException { + protected boolean doFilter(RuleContext ctx, DeviceToDeviceActorMsg msg) throws ScriptException { FromDeviceMsg deviceMsg = msg.getPayload(); if (deviceMsg instanceof TelemetryUploadRequest) { TelemetryUploadRequest telemetryMsg = (TelemetryUploadRequest) deviceMsg; diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java index eb00762eec..406c9fd600 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/DeviceTypeFilter.java @@ -16,8 +16,7 @@ package org.thingsboard.server.extensions.core.filter; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.component.Filter; import org.thingsboard.server.extensions.api.rules.RuleContext; import org.thingsboard.server.extensions.api.rules.RuleFilter; @@ -27,8 +26,6 @@ import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; -import static org.thingsboard.server.common.msg.session.MsgType.TO_SERVER_RPC_REQUEST; - /** * @author Andrew Shvayka */ @@ -46,7 +43,7 @@ public class DeviceTypeFilter extends SimpleRuleLifecycleComponent implements Ru } @Override - public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) { + public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) { return deviceTypes.contains(ctx.getDeviceMetaData().getDeviceType()); } } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java index e5672b8bca..daacfec3f4 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MethodNameFilter.java @@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.core.filter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.component.Filter; import org.thingsboard.server.extensions.api.rules.RuleContext; import org.thingsboard.server.extensions.api.rules.RuleFilter; @@ -27,7 +27,7 @@ import java.util.Arrays; import java.util.Set; import java.util.stream.Collectors; -import static org.thingsboard.server.common.msg.session.MsgType.TO_SERVER_RPC_REQUEST; +import static org.thingsboard.server.common.msg.session.SessionMsgType.TO_SERVER_RPC_REQUEST; /** * @author Andrew Shvayka @@ -46,7 +46,7 @@ public class MethodNameFilter extends SimpleRuleLifecycleComponent implements Ru } @Override - public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) { + public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) { if (msg.getPayload().getMsgType() == TO_SERVER_RPC_REQUEST) { return methods.contains(((ToServerRpcRequestMsg) msg.getPayload()).getMethod()); } else { diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java index ef0094e8a9..e40abf8bb6 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/MsgTypeFilter.java @@ -16,8 +16,9 @@ package org.thingsboard.server.extensions.core.filter; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.extensions.api.component.Filter; import org.thingsboard.server.extensions.api.rules.RuleContext; import org.thingsboard.server.extensions.api.rules.RuleFilter; @@ -35,30 +36,30 @@ import java.util.stream.Collectors; @Slf4j public class MsgTypeFilter extends SimpleRuleLifecycleComponent implements RuleFilter { - private List msgTypes; + private List sessionMsgTypes; @Override public void init(MsgTypeFilterConfiguration configuration) { - msgTypes = Arrays.stream(configuration.getMessageTypes()).map(type -> { + sessionMsgTypes = Arrays.stream(configuration.getMessageTypes()).map(type -> { switch (type) { case "GET_ATTRIBUTES": - return MsgType.GET_ATTRIBUTES_REQUEST; + return SessionMsgType.GET_ATTRIBUTES_REQUEST; case "POST_ATTRIBUTES": - return MsgType.POST_ATTRIBUTES_REQUEST; + return SessionMsgType.POST_ATTRIBUTES_REQUEST; case "POST_TELEMETRY": - return MsgType.POST_TELEMETRY_REQUEST; + return SessionMsgType.POST_TELEMETRY_REQUEST; case "RPC_REQUEST": - return MsgType.TO_SERVER_RPC_REQUEST; + return SessionMsgType.TO_SERVER_RPC_REQUEST; default: - throw new InvalidParameterException("Can't map " + type + " to " + MsgType.class.getName() + "!"); + throw new InvalidParameterException("Can't map " + type + " to " + SessionMsgType.class.getName() + "!"); } }).collect(Collectors.toList()); } @Override - public boolean filter(RuleContext ctx, ToDeviceActorMsg msg) { - for (MsgType msgType : msgTypes) { - if (msgType == msg.getPayload().getMsgType()) { + public boolean filter(RuleContext ctx, DeviceToDeviceActorMsg msg) { + for (SessionMsgType sessionMsgType : sessionMsgTypes) { + if (sessionMsgType == msg.getPayload().getMsgType()) { return true; } } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java index 20cb39763c..6e9fe3f1e7 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/filter/NashornJsEvaluator.java @@ -19,7 +19,7 @@ import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.msg.core.UpdateAttributesRequest; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; import org.thingsboard.server.extensions.api.device.DeviceAttributes; import javax.script.*; @@ -69,7 +69,7 @@ public class NashornJsEvaluator { return bindings; } - public static Bindings updateBindings(Bindings bindings, UpdateAttributesRequest msg) { + public static Bindings updateBindings(Bindings bindings, AttributesUpdateRequest msg) { Map attrMap = (Map) bindings.get(CLIENT_SIDE); for (AttributeKvEntry attr : msg.getAttributes()) { if (!CLIENT_SIDE.equalsIgnoreCase(attr.getKey()) && !SERVER_SIDE.equalsIgnoreCase(attr.getKey()) diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java index bd18b8b68d..5ef0650aa1 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/messaging/DeviceMessagingRuleMsgHandler.java @@ -26,15 +26,24 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RuleId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.msg.core.ToServerRpcRequestMsg; import org.thingsboard.server.common.msg.core.ToServerRpcResponseMsg; +import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginContext; import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; -import org.thingsboard.server.extensions.api.plugins.msg.*; +import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; +import org.thingsboard.server.extensions.api.plugins.msg.RpcError; +import org.thingsboard.server.extensions.api.plugins.msg.RpcResponsePluginToRuleMsg; +import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; import org.thingsboard.server.extensions.api.rules.RuleException; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; /** * @author Andrew Shvayka @@ -152,7 +161,7 @@ public class DeviceMessagingRuleMsgHandler implements RuleMsgHandler { pendingMsgs.put(uid, requestMd); log.trace("[{}] Forwarding {} to [{}]", uid, params, targetDeviceId); ToDeviceRpcRequestBody requestBody = new ToDeviceRpcRequestBody(ON_MSG_METHOD_NAME, GSON.toJson(params.get("body"))); - ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, null, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody)); + ctx.sendRpcRequest(new ToDeviceRpcRequest(uid, targetDevice.getTenantId(), targetDeviceId, oneWay, System.currentTimeMillis() + timeout, requestBody)); } else { replyWithError(ctx, requestMd, RpcError.FORBIDDEN); } diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcManager.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcManager.java deleted file mode 100644 index ff41de5efc..0000000000 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcManager.java +++ /dev/null @@ -1,69 +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.extensions.core.plugin.rpc; - -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.extensions.api.plugins.PluginContext; -import org.thingsboard.server.extensions.api.plugins.msg.*; -import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRestMsgHandler; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -/** - * @author Andrew Shvayka - */ -@Slf4j -public class RpcManager { - - @Setter - private RpcRestMsgHandler restHandler; - - private Map localRpcRequests = new HashMap<>(); - - public void process(PluginContext ctx, LocalRequestMetaData requestMd) { - ToDeviceRpcRequest request = requestMd.getRequest(); - log.trace("[{}] Processing local rpc call for device [{}]", request.getId(), request.getDeviceId()); - ctx.sendRpcRequest(request); - localRpcRequests.put(request.getId(), requestMd); - ctx.scheduleTimeoutMsg(new TimeoutUUIDMsg(request.getId(), request.getExpirationTime() - System.currentTimeMillis())); - } - - public void process(PluginContext ctx, FromDeviceRpcResponse response) { - UUID requestId = response.getId(); - LocalRequestMetaData md = localRpcRequests.remove(requestId); - if (md != null) { - log.trace("[{}] Processing local rpc response from device [{}]", requestId, md.getRequest().getDeviceId()); - restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), response); - } else { - log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response); - } - } - - public void process(PluginContext ctx, TimeoutMsg msg) { - if (msg instanceof TimeoutUUIDMsg) { - UUID requestId = ((TimeoutUUIDMsg) msg).getId(); - FromDeviceRpcResponse timeoutReponse = new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT); - LocalRequestMetaData md = localRpcRequests.remove(requestId); - if (md != null) { - log.trace("[{}] Processing rpc timeout for local device [{}]", requestId, md.getRequest().getDeviceId()); - restHandler.reply(ctx, md.getRequest(), md.getResponseWriter(), timeoutReponse); - } - } - } -} diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPlugin.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPlugin.java deleted file mode 100644 index de8caa17f5..0000000000 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/RpcPlugin.java +++ /dev/null @@ -1,86 +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.extensions.core.plugin.rpc; - -import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.extensions.api.component.Plugin; -import org.thingsboard.server.extensions.api.plugins.AbstractPlugin; -import org.thingsboard.server.extensions.api.plugins.PluginContext; -import org.thingsboard.server.extensions.api.plugins.handlers.DefaultRuleMsgHandler; -import org.thingsboard.server.extensions.api.plugins.handlers.RestMsgHandler; -import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; -import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; -import org.thingsboard.server.extensions.api.plugins.msg.TimeoutMsg; -import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallAction; -import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRestMsgHandler; -import org.thingsboard.server.extensions.core.plugin.rpc.handlers.RpcRuleMsgHandler; - -/** - * @author Andrew Shvayka - */ -@Plugin(name = "RPC Plugin", actions = {ServerSideRpcCallAction.class}, descriptor = "RpcPluginDescriptor.json", configuration = RpcPluginConfiguration.class) -@Slf4j -public class RpcPlugin extends AbstractPlugin { - - private final RpcManager rpcManager; - private final RpcRestMsgHandler restMsgHandler; - - public RpcPlugin() { - this.rpcManager = new RpcManager(); - this.restMsgHandler = new RpcRestMsgHandler(rpcManager); - this.rpcManager.setRestHandler(restMsgHandler); - } - - @Override - public void process(PluginContext ctx, FromDeviceRpcResponse msg) { - rpcManager.process(ctx, msg); - } - - @Override - public void process(PluginContext ctx, TimeoutMsg msg) { - rpcManager.process(ctx, msg); - } - - @Override - protected RestMsgHandler getRestMsgHandler() { - return restMsgHandler; - } - - @Override - public void init(RpcPluginConfiguration configuration) { - restMsgHandler.setDefaultTimeout(configuration.getDefaultTimeout()); - } - - @Override - protected RuleMsgHandler getRuleMsgHandler() { - return new RpcRuleMsgHandler(); - } - - @Override - public void resume(PluginContext ctx) { - //Do nothing - } - - @Override - public void suspend(PluginContext ctx) { - //Do nothing - } - - @Override - public void stop(PluginContext ctx) { - //Do nothing - } -} diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java deleted file mode 100644 index b2fbacadb0..0000000000 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRestMsgHandler.java +++ /dev/null @@ -1,161 +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.extensions.core.plugin.rpc.handlers; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; -import lombok.RequiredArgsConstructor; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.StringUtils; -import org.springframework.web.context.request.async.DeferredResult; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.extensions.api.exception.ToErrorResponseEntity; -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.handlers.DefaultRestMsgHandler; -import org.thingsboard.server.extensions.api.plugins.msg.FromDeviceRpcResponse; -import org.thingsboard.server.extensions.api.plugins.msg.RpcError; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody; -import org.thingsboard.server.extensions.api.plugins.rest.PluginRestMsg; -import org.thingsboard.server.extensions.api.plugins.rest.RestRequest; -import org.thingsboard.server.extensions.core.plugin.rpc.LocalRequestMetaData; -import org.thingsboard.server.extensions.core.plugin.rpc.RpcManager; -import org.thingsboard.server.extensions.core.plugin.rpc.cmd.RpcRequest; - -import javax.servlet.ServletException; -import java.io.IOException; -import java.util.Optional; -import java.util.UUID; - -/** - * @author Andrew Shvayka - */ -@Slf4j -@RequiredArgsConstructor -public class RpcRestMsgHandler extends DefaultRestMsgHandler { - - private final RpcManager rpcManager; - @Setter - private long defaultTimeout; - - @Override - public void handleHttpPostRequest(PluginContext ctx, PluginRestMsg msg) throws ServletException { - boolean valid = false; - RestRequest request = msg.getRequest(); - try { - String[] pathParams = request.getPathParams(); - if (pathParams.length == 2) { - String method = pathParams[0].toUpperCase(); - if (DataConstants.ONEWAY.equals(method) || DataConstants.TWOWAY.equals(method)) { - final TenantId tenantId = ctx.getSecurityCtx().orElseThrow(() -> new IllegalStateException("Security context is empty!")).getTenantId(); - JsonNode rpcRequestBody = jsonMapper.readTree(request.getRequestBody()); - - RpcRequest cmd = new RpcRequest(rpcRequestBody.get("method").asText(), - jsonMapper.writeValueAsString(rpcRequestBody.get("params"))); - if (rpcRequestBody.has("timeout")) { - cmd.setTimeout(rpcRequestBody.get("timeout").asLong()); - } - - boolean oneWay = DataConstants.ONEWAY.equals(method); - - DeviceId deviceId = DeviceId.fromString(pathParams[1]); - valid = handleDeviceRPCRequest(ctx, msg, tenantId, deviceId, cmd, oneWay); - } - } - } catch (IOException e) { - log.debug("Failed to process POST request due to IO exception", e); - } catch (RuntimeException e) { - log.debug("Failed to process POST request due to Runtime exception", e); - } - if (!valid) { - msg.getResponseHolder().setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); - } - } - - private boolean handleDeviceRPCRequest(PluginContext ctx, final PluginRestMsg msg, TenantId tenantId, DeviceId deviceId, RpcRequest cmd, boolean oneWay) throws JsonProcessingException { - long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout); - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); - ctx.checkAccess(deviceId, new PluginCallback() { - @Override - public void onSuccess(PluginContext ctx, Void value) { - ToDeviceRpcRequest rpcRequest = new ToDeviceRpcRequest(UUID.randomUUID(), - msg.getSecurityCtx(), - tenantId, - deviceId, - oneWay, - timeout, - body - ); - rpcManager.process(ctx, new LocalRequestMetaData(rpcRequest, msg.getResponseHolder())); - } - - @Override - public void onFailure(PluginContext ctx, Exception e) { - ResponseEntity response; - if (e instanceof ToErrorResponseEntity) { - response = ((ToErrorResponseEntity)e).toErrorResponseEntity(); - } else { - response = new ResponseEntity(HttpStatus.UNAUTHORIZED); - } - ctx.logRpcRequest(msg.getSecurityCtx(), deviceId, body, oneWay, Optional.empty(), e); - msg.getResponseHolder().setResult(response); - } - }); - return true; - } - - public void reply(PluginContext ctx, ToDeviceRpcRequest rpcRequest, DeferredResult responseWriter, FromDeviceRpcResponse response) { - Optional rpcError = response.getError(); - if (rpcError.isPresent()) { - ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); - RpcError error = rpcError.get(); - switch (error) { - case TIMEOUT: - responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); - break; - case NO_ACTIVE_CONNECTION: - responseWriter.setResult(new ResponseEntity<>(HttpStatus.CONFLICT)); - break; - default: - responseWriter.setResult(new ResponseEntity<>(HttpStatus.REQUEST_TIMEOUT)); - break; - } - } else { - Optional responseData = response.getResponse(); - if (responseData.isPresent() && !StringUtils.isEmpty(responseData.get())) { - String data = responseData.get(); - try { - ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); - responseWriter.setResult(new ResponseEntity<>(jsonMapper.readTree(data), HttpStatus.OK)); - } catch (IOException e) { - log.debug("Failed to decode device response: {}", data, e); - ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, e); - responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_ACCEPTABLE)); - } - } else { - ctx.logRpcRequest(rpcRequest.getSecurityCtx(), rpcRequest.getDeviceId(), rpcRequest.getBody(), rpcRequest.isOneway(), rpcError, null); - responseWriter.setResult(new ResponseEntity<>(HttpStatus.OK)); - } - } - } -} \ No newline at end of file diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRuleMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRuleMsgHandler.java deleted file mode 100644 index b7dd6675c4..0000000000 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/rpc/handlers/RpcRuleMsgHandler.java +++ /dev/null @@ -1,102 +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.extensions.core.plugin.rpc.handlers; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.extensions.api.plugins.PluginCallback; -import org.thingsboard.server.extensions.api.plugins.PluginContext; -import org.thingsboard.server.extensions.api.plugins.handlers.RuleMsgHandler; -import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequest; -import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody; -import org.thingsboard.server.extensions.api.rules.RuleException; -import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallActionMsg; -import org.thingsboard.server.extensions.core.action.rpc.ServerSideRpcCallRuleToPluginActionMsg; - -import java.util.Collections; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; - -/** - * Created by ashvayka on 14.09.17. - */ -@Slf4j -public class RpcRuleMsgHandler implements RuleMsgHandler { - - @Override - public void process(PluginContext ctx, TenantId tenantId, RuleId ruleId, RuleToPluginMsg msg) throws RuleException { - if (msg instanceof ServerSideRpcCallRuleToPluginActionMsg) { - handle(ctx, tenantId, ruleId, ((ServerSideRpcCallRuleToPluginActionMsg) msg).getPayload()); - } else { - throw new RuntimeException("Not supported msg: " + msg + "!"); - } - } - - private void handle(final PluginContext ctx, TenantId tenantId, RuleId ruleId, ServerSideRpcCallActionMsg msg) { - DeviceId deviceId = new DeviceId(UUID.fromString(msg.getDeviceId())); - ctx.checkAccess(deviceId, new PluginCallback() { - @Override - public void onSuccess(PluginContext dummy, Void value) { - try { - List deviceIds; - if (StringUtils.isEmpty(msg.getFromDeviceRelation()) && StringUtils.isEmpty(msg.getToDeviceRelation())) { - deviceIds = Collections.singletonList(deviceId); - } else if (!StringUtils.isEmpty(msg.getFromDeviceRelation())) { - List relations = ctx.findByFromAndType(deviceId, msg.getFromDeviceRelation()).get(); - deviceIds = relations.stream().map(EntityRelation::getTo).collect(Collectors.toList()); - } else { - List relations = ctx.findByToAndType(deviceId, msg.getFromDeviceRelation()).get(); - deviceIds = relations.stream().map(EntityRelation::getFrom).collect(Collectors.toList()); - } - ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(msg.getRpcCallMethod(), msg.getRpcCallBody()); - long expirationTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(msg.getRpcCallTimeoutInSec()); - for (EntityId address : deviceIds) { - DeviceId tmpId = new DeviceId(address.getId()); - ctx.checkAccess(tmpId, new PluginCallback() { - @Override - public void onSuccess(PluginContext ctx, Void value) { - ctx.sendRpcRequest(new ToDeviceRpcRequest(UUID.randomUUID(), - null, tenantId, tmpId, true, expirationTime, body) - ); - log.trace("[{}] Sent RPC Call Action msg", tmpId); - } - - @Override - public void onFailure(PluginContext ctx, Exception e) { - log.info("[{}] Failed to process RPC Call Action msg", tmpId, e); - } - }); - } - } catch (Exception e) { - log.info("Failed to process RPC Call Action msg", e); - } - } - - @Override - public void onFailure(PluginContext dummy, Exception e) { - log.info("[{}] Failed to process RPC Call Action msg", deviceId, e); - } - }); - } -} diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java index 9cb67fdffa..014d149d7a 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRuleMsgHandler.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.core.BasicGetAttributesResponse; import org.thingsboard.server.common.msg.core.BasicStatusCodeResponse; import org.thingsboard.server.common.msg.core.GetAttributesRequest; import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; -import org.thingsboard.server.common.msg.core.UpdateAttributesRequest; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; import org.thingsboard.server.common.msg.kv.BasicAttributeKVMsg; import org.thingsboard.server.extensions.api.plugins.PluginCallback; import org.thingsboard.server.extensions.api.plugins.PluginContext; @@ -132,7 +132,7 @@ public class TelemetryRuleMsgHandler extends DefaultRuleMsgHandler { @Override public void handleUpdateAttributesRequest(PluginContext ctx, TenantId tenantId, RuleId ruleId, UpdateAttributesRequestRuleToPluginMsg msg) { - UpdateAttributesRequest request = msg.getPayload(); + AttributesUpdateRequest request = msg.getPayload(); ctx.saveAttributes(msg.getTenantId(), msg.getDeviceId(), DataConstants.CLIENT_SCOPE, request.getAttributes().stream().collect(Collectors.toList()), new PluginCallback() { @Override diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java index f7ad2740c3..508ab6f889 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmDeduplicationProcessor.java @@ -22,7 +22,7 @@ import org.apache.velocity.VelocityContext; import org.apache.velocity.runtime.parser.ParseException; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Event; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.extensions.api.component.Processor; import org.thingsboard.server.extensions.api.rules.*; import org.thingsboard.server.extensions.core.utils.VelocityUtils; @@ -57,7 +57,7 @@ public class AlarmDeduplicationProcessor extends SimpleRuleLifecycleComponent } @Override - public RuleProcessingMetaData process(RuleContext ctx, ToDeviceActorMsg msg) throws RuleException { + public RuleProcessingMetaData process(RuleContext ctx, DeviceToDeviceActorMsg msg) throws RuleException { RuleProcessingMetaData md = new RuleProcessingMetaData(); VelocityContext context = VelocityUtils.createContext(ctx.getDeviceMetaData(), msg.getPayload()); String alarmId = VelocityUtils.merge(alarmIdTemplate, context); diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java index b4e34402ae..df6e45acd3 100644 --- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java +++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/processor/AlarmProcessor.java @@ -26,8 +26,8 @@ import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; -import org.thingsboard.server.common.msg.core.UpdateAttributesRequest; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceMsg; import org.thingsboard.server.extensions.api.component.Processor; import org.thingsboard.server.extensions.api.rules.RuleContext; @@ -102,7 +102,7 @@ public class AlarmProcessor implements RuleProcessor { @Override - protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + protected Optional buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) { KafkaActionPayload.KafkaActionPayloadBuilder builder = KafkaActionPayload.builder(); - builder.msgType(payload.getMsgType()); + builder.sessionMsgType(payload.getMsgType()); builder.requestId(payload.getRequestId()); builder.sync(configuration.isSync()); builder.topic(configuration.getTopic()); diff --git a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java index 3e87507150..4000c2286a 100644 --- a/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java +++ b/extensions/extension-kafka/src/main/java/org/thingsboard/server/extensions/kafka/plugin/KafkaMsgHandler.java @@ -49,10 +49,10 @@ public class KafkaMsgHandler implements RuleMsgHandler { if (payload.isSync()) { if (metadata != null) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId()))); } else { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onError(payload.getMsgType(), payload.getRequestId(), e))); + BasicStatusCodeResponse.onError(payload.getSessionMsgType(), payload.getRequestId(), e))); } } }); diff --git a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java index cee3ac310a..d7a0537686 100644 --- a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java +++ b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttActionPayload.java @@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.mqtt.action; import lombok.Builder; import lombok.Data; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import java.io.Serializable; @@ -30,5 +30,5 @@ public class MqttActionPayload implements Serializable { private final String msgBody; private final Integer requestId; - private final MsgType msgType; + private final SessionMsgType sessionMsgType; } diff --git a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java index a33fb47186..694fa9fccf 100644 --- a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java +++ b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/action/MqttPluginAction.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.extensions.mqtt.action; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; @@ -28,10 +28,10 @@ import java.util.Optional; public class MqttPluginAction extends AbstractTemplatePluginAction { @Override - protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + protected Optional buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) { MqttActionPayload.MqttActionPayloadBuilder builder = MqttActionPayload.builder(); builder.sync(configuration.isSync()); - builder.msgType(payload.getMsgType()); + builder.sessionMsgType(payload.getMsgType()); builder.requestId(payload.getRequestId()); builder.topic(configuration.getTopic()); builder.msgBody(getMsgBody(ctx, msg)); diff --git a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java index e6ac32a505..408372918f 100644 --- a/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java +++ b/extensions/extension-mqtt/src/main/java/org/thingsboard/server/extensions/mqtt/plugin/MqttMsgHandler.java @@ -51,7 +51,7 @@ public class MqttMsgHandler implements RuleMsgHandler { log.debug("Message [{}] was successfully delivered to topic [{}]!", msg.toString(), payload.getTopic()); if (payload.isSync()) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId()))); } } @Override @@ -59,7 +59,7 @@ public class MqttMsgHandler implements RuleMsgHandler { log.warn("Failed to deliver message [{}] to topic [{}]!", msg.toString(), payload.getTopic()); if (payload.isSync()) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onError(payload.getMsgType(), payload.getRequestId(), new Exception(e)))); + BasicStatusCodeResponse.onError(payload.getSessionMsgType(), payload.getRequestId(), new Exception(e)))); } } }); diff --git a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java index 948467323c..68ba7093be 100644 --- a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java +++ b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqActionPayload.java @@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.rabbitmq.action; import lombok.Builder; import lombok.Data; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import java.io.Serializable; @@ -35,5 +35,5 @@ public class RabbitMqActionPayload implements Serializable { private final boolean sync; private final Integer requestId; - private final MsgType msgType; + private final SessionMsgType sessionMsgType; } diff --git a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java index 90c6679590..0ae65b7f41 100644 --- a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java +++ b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/action/RabbitMqPluginAction.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.extensions.rabbitmq.action; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; @@ -32,13 +32,13 @@ import java.util.Optional; public class RabbitMqPluginAction extends AbstractTemplatePluginAction { @Override - protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + protected Optional buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) { RabbitMqActionPayload.RabbitMqActionPayloadBuilder builder = RabbitMqActionPayload.builder(); builder.sync(configuration.isSync()); builder.exchange(configuration.getExchange()); builder.queueName(configuration.getQueueName()); builder.messageProperties(configuration.getMessageProperties()); - builder.msgType(payload.getMsgType()); + builder.sessionMsgType(payload.getMsgType()); builder.requestId(payload.getRequestId()); builder.payload(getMsgBody(ctx, msg)); return Optional.of(new RabbitMqActionMsg(msg.getTenantId(), diff --git a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java index 764dd0d04b..b964ee1b0a 100644 --- a/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java +++ b/extensions/extension-rabbitmq/src/main/java/org/thingsboard/server/extensions/rabbitmq/plugin/RabbitMqMsgHandler.java @@ -57,7 +57,7 @@ public class RabbitMqMsgHandler implements RuleMsgHandler { payload.getPayload().getBytes(UTF8)); if (payload.isSync()) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId()))); } } catch (IOException e) { throw new RuleException(e.getMessage(), e); diff --git a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java index 996ef366ed..8a5a5b776d 100644 --- a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java +++ b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallActionPayload.java @@ -19,7 +19,8 @@ import lombok.Builder; import lombok.Data; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import java.io.Serializable; @@ -33,5 +34,5 @@ public class RestApiCallActionPayload implements Serializable { private final boolean sync; private final Integer requestId; - private final MsgType msgType; + private final SessionMsgType sessionMsgType; } diff --git a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java index eec95d1bce..6e60078e86 100644 --- a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java +++ b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/action/RestApiCallPluginAction.java @@ -18,7 +18,7 @@ package org.thingsboard.server.extensions.rest.action; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; @@ -33,9 +33,9 @@ import java.util.Optional; public class RestApiCallPluginAction extends AbstractTemplatePluginAction { @Override - protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + protected Optional buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) { RestApiCallActionPayload.RestApiCallActionPayloadBuilder builder = RestApiCallActionPayload.builder(); - builder.msgType(payload.getMsgType()); + builder.sessionMsgType(payload.getMsgType()); builder.requestId(payload.getRequestId()); builder.sync(configuration.isSync()); builder.actionPath(configuration.getActionPath()); diff --git a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java index bc7cc719fd..7c04a43859 100644 --- a/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java +++ b/extensions/extension-rest-api-call/src/main/java/org/thingsboard/server/extensions/rest/plugin/RestApiCallMsgHandler.java @@ -52,7 +52,7 @@ public class RestApiCallMsgHandler implements RuleMsgHandler { String.class); if (exchangeResponse.getStatusCode().equals(payload.getExpectedResultCode()) && payload.isSync()) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId()))); } else if(!exchangeResponse.getStatusCode().equals(payload.getExpectedResultCode())) { throw new RuntimeException("Response Status Code '" + exchangeResponse.getStatusCode() diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java index afc0c45a7a..6b7caaf530 100644 --- a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicActionPayload.java @@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.sns.action; import lombok.Builder; import lombok.Data; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import java.io.Serializable; @@ -32,6 +32,6 @@ public class SnsTopicActionPayload implements Serializable { private final String msgBody; private final Integer requestId; - private final MsgType msgType; + private final SessionMsgType sessionMsgType; private final boolean sync; } diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java index 13add7db90..c65ed990b9 100644 --- a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/action/SnsTopicPluginAction.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.extensions.sns.action; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; @@ -31,9 +31,9 @@ import java.util.Optional; public class SnsTopicPluginAction extends AbstractTemplatePluginAction { @Override - protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + protected Optional buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) { SnsTopicActionPayload.SnsTopicActionPayloadBuilder builder = SnsTopicActionPayload.builder(); - builder.msgType(payload.getMsgType()); + builder.sessionMsgType(payload.getMsgType()); builder.requestId(payload.getRequestId()); builder.topicArn(configuration.getTopicArn()); builder.msgBody(getMsgBody(ctx, msg)); diff --git a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java index d84208a8c2..b90bb7802f 100644 --- a/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java +++ b/extensions/extension-sns/src/main/java/org/thingsboard/server/extensions/sns/plugin/SnsMessageHandler.java @@ -52,7 +52,7 @@ public class SnsMessageHandler implements RuleMsgHandler { sns.publish(publishRequest); if (payload.isSync()) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId()))); } return; } diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java index 6c1b40c3bf..deafcc70c7 100644 --- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java +++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueueActionPayload.java @@ -17,7 +17,7 @@ package org.thingsboard.server.extensions.sqs.action.fifo; import lombok.Builder; import lombok.Data; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import java.io.Serializable; @@ -33,7 +33,7 @@ public class SqsFifoQueueActionPayload implements Serializable { private final String deviceId; private final Integer requestId; - private final MsgType msgType; + private final SessionMsgType sessionMsgType; private final boolean sync; } diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java index e834c6d263..29c14e1262 100644 --- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java +++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/fifo/SqsFifoQueuePluginAction.java @@ -15,15 +15,12 @@ */ package org.thingsboard.server.extensions.sqs.action.fifo; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; import org.thingsboard.server.extensions.api.rules.RuleContext; import org.thingsboard.server.extensions.core.action.template.AbstractTemplatePluginAction; -import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionMsg; -import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueueActionPayload; -import org.thingsboard.server.extensions.sqs.action.standard.SqsStandardQueuePluginActionConfiguration; import java.util.Optional; @@ -34,9 +31,9 @@ import java.util.Optional; public class SqsFifoQueuePluginAction extends AbstractTemplatePluginAction { @Override - protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + protected Optional buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) { SqsFifoQueueActionPayload.SqsFifoQueueActionPayloadBuilder builder = SqsFifoQueueActionPayload.builder(); - builder.msgType(payload.getMsgType()); + builder.sessionMsgType(payload.getMsgType()); builder.requestId(payload.getRequestId()); builder.queue(configuration.getQueue()); builder.deviceId(msg.getDeviceId().toString()); diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java index 3e35562de5..d6f81d37fd 100644 --- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java +++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueueActionPayload.java @@ -17,7 +17,8 @@ package org.thingsboard.server.extensions.sqs.action.standard; import lombok.Builder; import lombok.Data; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import java.io.Serializable; @@ -33,7 +34,7 @@ public class SqsStandardQueueActionPayload implements Serializable { private final int delaySeconds; private final Integer requestId; - private final MsgType msgType; + private final SessionMsgType sessionMsgType; private final boolean sync; } diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java index 232e26349d..4ebf7bb743 100644 --- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java +++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/action/standard/SqsStandardQueuePluginAction.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.extensions.sqs.action.standard; -import org.thingsboard.server.common.msg.device.ToDeviceActorMsg; +import org.thingsboard.server.common.msg.device.DeviceToDeviceActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceRequestMsg; import org.thingsboard.server.extensions.api.component.Action; import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; @@ -31,9 +31,9 @@ import java.util.Optional; public class SqsStandardQueuePluginAction extends AbstractTemplatePluginAction { @Override - protected Optional buildRuleToPluginMsg(RuleContext ctx, ToDeviceActorMsg msg, FromDeviceRequestMsg payload) { + protected Optional buildRuleToPluginMsg(RuleContext ctx, DeviceToDeviceActorMsg msg, FromDeviceRequestMsg payload) { SqsStandardQueueActionPayload.SqsStandardQueueActionPayloadBuilder builder = SqsStandardQueueActionPayload.builder(); - builder.msgType(payload.getMsgType()); + builder.sessionMsgType(payload.getMsgType()); builder.requestId(payload.getRequestId()); builder.queue(configuration.getQueue()); builder.delaySeconds(configuration.getDelaySeconds()); diff --git a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java index d049b57dac..248996aba9 100644 --- a/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java +++ b/extensions/extension-sqs/src/main/java/org/thingsboard/server/extensions/sqs/plugin/SqsMessageHandler.java @@ -65,7 +65,7 @@ public class SqsMessageHandler implements RuleMsgHandler { sqs.sendMessage(sendMsgRequest); if (payload.isSync()) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId()))); } } @@ -78,7 +78,7 @@ public class SqsMessageHandler implements RuleMsgHandler { sqs.sendMessage(sendMsgRequest); if (payload.isSync()) { ctx.reply(new ResponsePluginToRuleMsg(msg.getUid(), tenantId, ruleId, - BasicStatusCodeResponse.onSuccess(payload.getMsgType(), payload.getRequestId()))); + BasicStatusCodeResponse.onSuccess(payload.getSessionMsgType(), payload.getRequestId()))); } } } diff --git a/pom.xml b/pom.xml index dc58627047..67d48b268b 100755 --- a/pom.xml +++ b/pom.xml @@ -79,6 +79,7 @@ 2.5.3 1.2.1 9.4.1211 + 2.0.0TB org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* @@ -818,6 +819,11 @@ exe provided + + nl.jk5.netty-mqtt + netty-mqtt + ${netty-mqtt-client.version} + org.elasticsearch.client rest diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java new file mode 100644 index 0000000000..9553b13c24 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/EmptyNodeConfiguration.java @@ -0,0 +1,35 @@ +/** + * 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.rule.engine.api; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Data +public class EmptyNodeConfiguration implements NodeConfiguration { + + private int version; + + @Override + public EmptyNodeConfiguration defaultConfiguration() { + EmptyNodeConfiguration configuration = new EmptyNodeConfiguration(); + return configuration; + } +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java new file mode 100644 index 0000000000..5ea481aa46 --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java @@ -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.rule.engine.api; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceId; + +/** + * Created by ashvayka on 02.04.18. + */ +@Data +@Builder +public final class RuleEngineDeviceRpcRequest { + + private final DeviceId deviceId; + private final int requestId; + private final boolean oneway; + private final String method; + private final String body; + private final long timeout; + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java new file mode 100644 index 0000000000..f67831747c --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcResponse.java @@ -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.rule.engine.api; + +import lombok.Builder; +import lombok.Data; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.extensions.api.plugins.msg.RpcError; + +import java.util.Optional; + +/** + * Created by ashvayka on 02.04.18. + */ +@Data +@Builder +public final class RuleEngineDeviceRpcResponse { + + private final DeviceId deviceId; + private final int requestId; + private final Optional response; + private final Optional error; + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/RuleControllerSqlTest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineRpcService.java similarity index 61% rename from application/src/test/java/org/thingsboard/server/controller/sql/RuleControllerSqlTest.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineRpcService.java index cf651dac02..df9d8d9392 100644 --- a/application/src/test/java/org/thingsboard/server/controller/sql/RuleControllerSqlTest.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineRpcService.java @@ -13,14 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.controller.sql; +package org.thingsboard.rule.engine.api; -import org.thingsboard.server.controller.BaseRuleControllerTest; -import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.common.data.id.DeviceId; +import java.util.function.Consumer; /** - * Created by Valerii Sosliuk on 6/28/2017. + * Created by ashvayka on 02.04.18. */ -@DaoSqlTest -public class RuleControllerSqlTest extends BaseRuleControllerTest { +public interface RuleEngineRpcService { + + void sendRpcReply(DeviceId deviceId, int requestId, String body); + + void sendRpcRequest(RuleEngineDeviceRpcRequest request, Consumer consumer); + } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java index 1ba18cd52f..aa57a027b8 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java @@ -33,4 +33,12 @@ public interface RuleEngineTelemetryService { void saveAndNotify(EntityId entityId, String scope, List attributes, FutureCallback callback); + void saveAttrAndNotify(EntityId entityId, String scope, String key, long value, FutureCallback callback); + + void saveAttrAndNotify(EntityId entityId, String scope, String key, String value, FutureCallback callback); + + void saveAttrAndNotify(EntityId entityId, String scope, String key, double value, FutureCallback callback); + + void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback callback); + } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 6038e6d84f..962f8aad51 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -15,10 +15,12 @@ */ package org.thingsboard.rule.engine.api; +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.rule.RuleNode; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; @@ -42,6 +44,8 @@ public interface TbContext { void tellNext(TbMsg msg, String relationType); + void tellNext(TbMsg msg, String relationType, Throwable th); + void tellNext(TbMsg msg, Set relationTypes); void tellSelf(TbMsg msg, long delayMs); @@ -58,6 +62,10 @@ public interface TbContext { void updateSelf(RuleNode self); + TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data); + + TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data); + RuleNodeId getSelfId(); TenantId getTenantId(); @@ -78,6 +86,8 @@ public interface TbContext { RuleChainService getRuleChainService(); + RuleEngineRpcService getRpcService(); + RuleEngineTelemetryService getTelemetryService(); TimeseriesService getTimeseriesService(); @@ -90,6 +100,8 @@ public interface TbContext { ListeningExecutor getDbCallbackExecutor(); + ListeningExecutor getExternalCallExecutor(); + MailService getMailService(); ScriptEngine createJsScriptEngine(String script, String functionName, String... argNames); diff --git a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutIntMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbRelationTypes.java similarity index 72% rename from extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutIntMsg.java rename to rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbRelationTypes.java index e25d5d7dd2..e6455da04f 100644 --- a/extensions-api/src/main/java/org/thingsboard/server/extensions/api/plugins/msg/TimeoutIntMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbRelationTypes.java @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.extensions.api.plugins.msg; +package org.thingsboard.rule.engine.api; /** - * @author Andrew Shvayka + * Created by ashvayka on 19.01.18. */ -public final class TimeoutIntMsg extends TimeoutMsg { +public final class TbRelationTypes { - public TimeoutIntMsg(Integer id, long timeout) { - super(id, timeout); - } + public static String SUCCESS = "Success"; + public static String FAILURE = "Failure"; } diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 419b5f6ef3..a38df289d3 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -35,6 +35,7 @@ UTF-8 ${basedir}/../.. + 1.11.323 @@ -81,6 +82,32 @@ velocity-tools provided + + org.springframework + spring-web + provided + + + org.apache.kafka + kafka_2.10 + + + com.amazonaws + aws-java-sdk-sns + ${aws.sdk.version} + + + com.rabbitmq + amqp-client + + + nl.jk5.netty-mqtt + netty-mqtt + + + org.bouncycastle + bcpkix-jdk15on + junit junit diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java index d6f43846f2..19e8013ada 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/TbNodeUtils.java @@ -19,6 +19,9 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.Map; /** * Created by ashvayka on 19.01.18. @@ -27,6 +30,9 @@ public class TbNodeUtils { private static final ObjectMapper mapper = new ObjectMapper(); + private static final String VARIABLE_TEMPLATE = "${%s}"; + + public static T convert(TbNodeConfiguration configuration, Class clazz) throws TbNodeException { try { return mapper.treeToValue(configuration.getData(), clazz); @@ -35,4 +41,17 @@ public class TbNodeUtils { } } + public static String processPattern(String pattern, TbMsgMetaData metaData) { + String result = new String(pattern); + for (Map.Entry keyVal : metaData.values().entrySet()) { + result = processVar(result, keyVal.getKey(), keyVal.getValue()); + } + return result; + } + + private static String processVar(String pattern, String key, String val) { + String varPattern = String.format(VARIABLE_TEMPLATE, key); + return pattern.replace(varPattern, val); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java index a242093efc..566a125bd8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAlarmNode.java @@ -15,7 +15,6 @@ */ package org.thingsboard.rule.engine.action; -import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Function; @@ -32,8 +31,6 @@ import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.util.concurrent.ExecutorService; - import static org.thingsboard.rule.engine.DonAsynchron.withCallback; @Slf4j @@ -91,11 +88,11 @@ public class TbAlarmNode implements TbNode { if (alarmResult.alarm == null) { ctx.tellNext(msg, "False"); } else if (alarmResult.isCreated) { - ctx.tellNext(toAlarmMsg(alarmResult, msg), "Created"); + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"); } else if (alarmResult.isUpdated) { - ctx.tellNext(toAlarmMsg(alarmResult, msg), "Updated"); + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated"); } else if (alarmResult.isCleared) { - ctx.tellNext(toAlarmMsg(alarmResult, msg), "Cleared"); + ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); } }, t -> ctx.tellError(msg, t)); @@ -176,7 +173,7 @@ public class TbAlarmNode implements TbNode { return ctx.getJsExecutor().executeAsync(() -> buildDetailsJsEngine.executeJson(msg)); } - private TbMsg toAlarmMsg(AlarmResult alarmResult, TbMsg originalMsg) { + private TbMsg toAlarmMsg(TbContext ctx, AlarmResult alarmResult, TbMsg originalMsg) { JsonNode jsonNodes = mapper.valueToTree(alarmResult.alarm); String data = jsonNodes.toString(); TbMsgMetaData metaData = originalMsg.getMetaData().copy(); @@ -187,7 +184,7 @@ public class TbAlarmNode implements TbNode { } else if (alarmResult.isCleared) { metaData.putValue(IS_CLEARED_ALARM, Boolean.TRUE.toString()); } - return new TbMsg(UUIDs.timeBased(), "ALARM", originalMsg.getOriginator(), metaData, data); + return ctx.transformMsg(originalMsg, "ALARM", originalMsg.getOriginator(), metaData, data); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java index aafb7f12c1..46c856418c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java @@ -26,7 +26,7 @@ public class TbLogNodeConfiguration implements NodeConfiguration { @Override public TbLogNodeConfiguration defaultConfiguration() { TbLogNodeConfiguration configuration = new TbLogNodeConfiguration(); - configuration.setJsScript("return 'incoming message = ' + msg + meta;"); + configuration.setJsScript("return 'Incoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java new file mode 100644 index 0000000000..428621cf67 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNode.java @@ -0,0 +1,119 @@ +/** + * 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.rule.engine.aws.sns; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.sns.AmazonSNS; +import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.model.PublishRequest; +import com.amazonaws.services.sns.model.PublishResult; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.concurrent.ExecutionException; + +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "aws sns", + configClazz = TbSnsNodeConfiguration.class, + nodeDescription = "Publish messages to AWS SNS", + nodeDetails = "Expects messages with any message type. Will publish message to AWS SNS topic.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeSnsConfig" +) +public class TbSnsNode implements TbNode { + + private static final String MESSAGE_ID = "messageId"; + private static final String REQUEST_ID = "requestId"; + private static final String ERROR = "error"; + + private TbSnsNodeConfiguration config; + private AmazonSNS snsClient; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbSnsNodeConfiguration.class); + AWSCredentials awsCredentials = new BasicAWSCredentials(this.config.getAccessKeyId(), this.config.getSecretAccessKey()); + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); + try { + this.snsClient = AmazonSNSClient.builder() + .withCredentials(credProvider) + .withRegion(this.config.getRegion()) + .build(); + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { + withCallback(publishMessageAsync(ctx, msg), + m -> ctx.tellNext(m, TbRelationTypes.SUCCESS), + t -> { + TbMsg next = processException(ctx, msg, t); + ctx.tellNext(next, TbRelationTypes.FAILURE, t); + }); + } + + ListenableFuture publishMessageAsync(TbContext ctx, TbMsg msg) { + return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg)); + } + + TbMsg publishMessage(TbContext ctx, TbMsg msg) { + String topicArn = TbNodeUtils.processPattern(this.config.getTopicArnPattern(), msg.getMetaData()); + PublishRequest publishRequest = new PublishRequest() + .withTopicArn(topicArn) + .withMessage(msg.getData()); + PublishResult result = this.snsClient.publish(publishRequest); + return processPublishResult(ctx, msg, result); + } + + private TbMsg processPublishResult(TbContext ctx, TbMsg origMsg, PublishResult result) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(MESSAGE_ID, result.getMessageId()); + metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + @Override + public void destroy() { + if (this.snsClient != null) { + try { + this.snsClient.shutdown(); + } catch (Exception e) { + log.error("Failed to shutdown SNS client during destroy()", e); + } + } + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNodeConfiguration.java new file mode 100644 index 0000000000..a1247789a4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sns/TbSnsNodeConfiguration.java @@ -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.rule.engine.aws.sns; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +@Data +public class TbSnsNodeConfiguration implements NodeConfiguration { + + private String topicArnPattern; + private String accessKeyId; + private String secretAccessKey; + private String region; + + @Override + public TbSnsNodeConfiguration defaultConfiguration() { + TbSnsNodeConfiguration configuration = new TbSnsNodeConfiguration(); + configuration.setTopicArnPattern("arn:aws:sns:us-east-1:123456789012:MyNewTopic"); + configuration.setRegion("us-east-1"); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java new file mode 100644 index 0000000000..f5172c7cc3 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNode.java @@ -0,0 +1,147 @@ +/** + * 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.rule.engine.aws.sqs; + +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.AWSStaticCredentialsProvider; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.sqs.AmazonSQS; +import com.amazonaws.services.sqs.AmazonSQSClientBuilder; +import com.amazonaws.services.sqs.model.MessageAttributeValue; +import com.amazonaws.services.sqs.model.SendMessageRequest; +import com.amazonaws.services.sqs.model.SendMessageResult; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "aws sqs", + configClazz = TbSqsNodeConfiguration.class, + nodeDescription = "Publish messages to AWS SQS", + nodeDetails = "Expects messages with any message type. Will publish message to AWS SQS queue.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeSqsConfig" +) +public class TbSqsNode implements TbNode { + + private static final String MESSAGE_ID = "messageId"; + private static final String REQUEST_ID = "requestId"; + private static final String MESSAGE_BODY_MD5 = "messageBodyMd5"; + private static final String MESSAGE_ATTRIBUTES_MD5 = "messageAttributesMd5"; + private static final String SEQUENCE_NUMBER = "sequenceNumber"; + private static final String ERROR = "error"; + + private TbSqsNodeConfiguration config; + private AmazonSQS sqsClient; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbSqsNodeConfiguration.class); + AWSCredentials awsCredentials = new BasicAWSCredentials(this.config.getAccessKeyId(), this.config.getSecretAccessKey()); + AWSStaticCredentialsProvider credProvider = new AWSStaticCredentialsProvider(awsCredentials); + try { + this.sqsClient = AmazonSQSClientBuilder.standard() + .withCredentials(credProvider) + .withRegion(this.config.getRegion()) + .build(); + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { + withCallback(publishMessageAsync(ctx, msg), + m -> ctx.tellNext(m, TbRelationTypes.SUCCESS), + t -> { + TbMsg next = processException(ctx, msg, t); + ctx.tellNext(next, TbRelationTypes.FAILURE, t); + }); + } + + ListenableFuture publishMessageAsync(TbContext ctx, TbMsg msg) { + return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg)); + } + + TbMsg publishMessage(TbContext ctx, TbMsg msg) { + String queueUrl = TbNodeUtils.processPattern(this.config.getQueueUrlPattern(), msg.getMetaData()); + SendMessageRequest sendMsgRequest = new SendMessageRequest(); + sendMsgRequest.withQueueUrl(queueUrl); + sendMsgRequest.withMessageBody(msg.getData()); + Map messageAttributes = new HashMap<>(); + this.config.getMessageAttributes().forEach((k,v) -> { + String name = TbNodeUtils.processPattern(k, msg.getMetaData()); + String val = TbNodeUtils.processPattern(v, msg.getMetaData()); + messageAttributes.put(name, new MessageAttributeValue().withDataType("String").withStringValue(val)); + }); + sendMsgRequest.setMessageAttributes(messageAttributes); + if (this.config.getQueueType() == TbSqsNodeConfiguration.QueueType.STANDARD) { + sendMsgRequest.withDelaySeconds(this.config.getDelaySeconds()); + } else { + sendMsgRequest.withMessageDeduplicationId(msg.getId().toString()); + sendMsgRequest.withMessageGroupId(msg.getOriginator().toString()); + } + SendMessageResult result = this.sqsClient.sendMessage(sendMsgRequest); + return processSendMessageResult(ctx, msg, result); + } + + private TbMsg processSendMessageResult(TbContext ctx, TbMsg origMsg, SendMessageResult result) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(MESSAGE_ID, result.getMessageId()); + metaData.putValue(REQUEST_ID, result.getSdkResponseMetadata().getRequestId()); + if (!StringUtils.isEmpty(result.getMD5OfMessageBody())) { + metaData.putValue(MESSAGE_BODY_MD5, result.getMD5OfMessageBody()); + } + if (!StringUtils.isEmpty(result.getMD5OfMessageAttributes())) { + metaData.putValue(MESSAGE_ATTRIBUTES_MD5, result.getMD5OfMessageAttributes()); + } + if (!StringUtils.isEmpty(result.getSequenceNumber())) { + metaData.putValue(SEQUENCE_NUMBER, result.getSequenceNumber()); + } + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + @Override + public void destroy() { + if (this.sqsClient != null) { + try { + this.sqsClient.shutdown(); + } catch (Exception e) { + log.error("Failed to shutdown SQS client during destroy()", e); + } + } + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNodeConfiguration.java new file mode 100644 index 0000000000..d27af36216 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/aws/sqs/TbSqsNodeConfiguration.java @@ -0,0 +1,51 @@ +/** + * 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.rule.engine.aws.sqs; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +import java.util.Collections; +import java.util.Map; + +@Data +public class TbSqsNodeConfiguration implements NodeConfiguration { + + private QueueType queueType; + private String queueUrlPattern; + private int delaySeconds; + private Map messageAttributes; + private String accessKeyId; + private String secretAccessKey; + private String region; + + @Override + public TbSqsNodeConfiguration defaultConfiguration() { + TbSqsNodeConfiguration configuration = new TbSqsNodeConfiguration(); + configuration.setQueueType(QueueType.STANDARD); + configuration.setQueueUrlPattern("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue-name"); + configuration.setDelaySeconds(0); + configuration.setMessageAttributes(Collections.emptyMap()); + configuration.setRegion("us-east-1"); + return configuration; + } + + public enum QueueType { + STANDARD, + FIFO + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 65b13710e3..04fa7281fa 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -15,7 +15,6 @@ */ package org.thingsboard.rule.engine.debug; -import com.datastax.driver.core.utils.UUIDs; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -78,18 +77,18 @@ public class TbMsgGeneratorNode implements TbNode { } private void sentTickMsg(TbContext ctx) { - TbMsg tickMsg = new TbMsg(UUIDs.timeBased(), TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), ""); + TbMsg tickMsg = ctx.newMsg(TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), ""); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, delay); } - protected ListenableFuture generate(TbContext ctx) { + private ListenableFuture generate(TbContext ctx) { return ctx.getJsExecutor().executeAsync(() -> { if (prevMsg == null) { - prevMsg = new TbMsg(UUIDs.timeBased(), "", originatorId, new TbMsgMetaData(), "{}"); + prevMsg = ctx.newMsg( "", originatorId, new TbMsgMetaData(), "{}"); } TbMsg generated = jsEngine.executeGenerate(prevMsg); - prevMsg = new TbMsg(UUIDs.timeBased(), generated.getType(), originatorId, generated.getMetaData(), generated.getData()); + prevMsg = ctx.newMsg(generated.getType(), originatorId, generated.getMetaData(), generated.getData()); return prevMsg; }); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 3c6704b013..4ee8bd5461 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -61,7 +61,7 @@ public class TbJsSwitchNode implements TbNode { ctx.tellNext(msg, nextRelations); } - @Override + @Override public void destroy() { if (jsEngine != null) { jsEngine.destroy(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java index 225cd99e36..6426106d0a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeFilterNode.java @@ -31,8 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg; configClazz = TbMsgTypeFilterNodeConfiguration.class, relationTypes = {"True", "False"}, nodeDescription = "Filter incoming messages by Message Type", - nodeDetails = "Evaluate incoming Message with configured JS condition. " + - "If incoming MessageType is expected - send Message via Success chain, otherwise Failure chain is used.", + nodeDetails = "If incoming MessageType is expected - send Message via Success chain, otherwise Failure chain is used.", uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, configDirective = "tbFilterNodeMessageTypeConfig") public class TbMsgTypeFilterNode implements TbNode { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java new file mode 100644 index 0000000000..75329a031c --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -0,0 +1,72 @@ +/** + * 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.rule.engine.filter; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.session.SessionMsgType; + +@Slf4j +@RuleNode( + type = ComponentType.FILTER, + name = "message type switch", + configClazz = EmptyNodeConfiguration.class, + relationTypes = {"Post attributes", "Post telemetry", "RPC Request", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Other"}, + nodeDescription = "Route incoming messages by Message Type", + nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\" via corresponding chain, otherwise Other chain is used.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbNodeEmptyConfig") +public class TbMsgTypeSwitchNode implements TbNode { + + EmptyNodeConfiguration config; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, EmptyNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException { + String relationType; + if (msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { + relationType = "Post attributes"; + } else if (msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { + relationType = "Post telemetry"; + } else if (msg.getType().equals(SessionMsgType.TO_SERVER_RPC_REQUEST.name())) { + relationType = "RPC Request"; + } else if (msg.getType().equals(DataConstants.ACTIVITY_EVENT)) { + relationType = "Activity Event"; + } else if (msg.getType().equals(DataConstants.INACTIVITY_EVENT)) { + relationType = "Inactivity Event"; + } else if (msg.getType().equals(DataConstants.CONNECT_EVENT)) { + relationType = "Connect Event"; + } else if (msg.getType().equals(DataConstants.DISCONNECT_EVENT)) { + relationType = "Disconnect Event"; + } else { + relationType = "Other"; + } + ctx.tellNext(msg, relationType); + } + + @Override + public void destroy() { + + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java new file mode 100644 index 0000000000..ff54c385ee --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -0,0 +1,117 @@ +/** + * 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.rule.engine.kafka; + +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.producer.*; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.Properties; +import java.util.concurrent.ExecutionException; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "kafka", + configClazz = TbKafkaNodeConfiguration.class, + nodeDescription = "Publish messages to Kafka server", + nodeDetails = "Expects messages with any message type. Will send record via Kafka producer to Kafka server.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeKafkaConfig" +) +public class TbKafkaNode implements TbNode { + + private static final String OFFSET = "offset"; + private static final String PARTITION = "partition"; + private static final String TOPIC = "topic"; + private static final String ERROR = "error"; + + private TbKafkaNodeConfiguration config; + + private Producer producer; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class); + Properties properties = new Properties(); + properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers()); + properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, config.getValueSerializer()); + properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, config.getKeySerializer()); + properties.put(ProducerConfig.ACKS_CONFIG, config.getAcks()); + properties.put(ProducerConfig.RETRIES_CONFIG, config.getRetries()); + properties.put(ProducerConfig.BATCH_SIZE_CONFIG, config.getBatchSize()); + properties.put(ProducerConfig.LINGER_MS_CONFIG, config.getLinger()); + properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, config.getBufferMemory()); + if (config.getOtherProperties() != null) { + config.getOtherProperties() + .forEach((k,v) -> properties.put(k, v)); + } + try { + this.producer = new KafkaProducer<>(properties); + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { + String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg.getMetaData()); + try { + producer.send(new ProducerRecord<>(topic, msg.getData()), + (metadata, e) -> { + if (metadata != null) { + TbMsg next = processResponse(ctx, msg, metadata); + ctx.tellNext(next, TbRelationTypes.SUCCESS); + } else { + TbMsg next = processException(ctx, msg, e); + ctx.tellNext(next, TbRelationTypes.FAILURE, e); + } + }); + } catch (Exception e) { + ctx.tellError(msg, e); + } + } + + @Override + public void destroy() { + if (this.producer != null) { + try { + this.producer.close(); + } catch (Exception e) { + log.error("Failed to close producer during destroy()", e); + } + } + } + + private TbMsg processResponse(TbContext ctx, TbMsg origMsg, RecordMetadata recordMetadata) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(OFFSET, String.valueOf(recordMetadata.offset())); + metaData.putValue(PARTITION, String.valueOf(recordMetadata.partition())); + metaData.putValue(TOPIC, recordMetadata.topic()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + private TbMsg processException(TbContext ctx, TbMsg origMsg, Exception e) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java new file mode 100644 index 0000000000..2c16e56f39 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java @@ -0,0 +1,55 @@ +/** + * 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.rule.engine.kafka; + +import lombok.Data; +import org.apache.kafka.common.serialization.StringSerializer; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +import java.util.Collections; +import java.util.Map; + +@Data +public class TbKafkaNodeConfiguration implements NodeConfiguration { + + private String topicPattern; + private String bootstrapServers; + private int retries; + private int batchSize; + private int linger; + private int bufferMemory; + private String acks; + private String keySerializer; + private String valueSerializer; + private Map otherProperties; + + @Override + public TbKafkaNodeConfiguration defaultConfiguration() { + TbKafkaNodeConfiguration configuration = new TbKafkaNodeConfiguration(); + configuration.setTopicPattern("my-topic"); + configuration.setBootstrapServers("localhost:9092"); + configuration.setRetries(0); + configuration.setBatchSize(16384); + configuration.setLinger(0); + configuration.setBufferMemory(33554432); + configuration.setAcks("-1"); + configuration.setKeySerializer(StringSerializer.class.getName()); + configuration.setValueSerializer(StringSerializer.class.getName()); + configuration.setOtherProperties(Collections.emptyMap()); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index cae2058417..69c0901057 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -15,7 +15,6 @@ */ package org.thingsboard.rule.engine.mail; -import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @@ -76,7 +75,7 @@ public class TbMsgToEmailNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) { try { EmailPojo email = convert(msg); - TbMsg emailMsg = buildEmailMsg(msg, email); + TbMsg emailMsg = buildEmailMsg(ctx, msg, email); ctx.tellNext(emailMsg); } catch (Exception ex) { log.warn("Can not convert message to email " + ex.getMessage()); @@ -84,9 +83,9 @@ public class TbMsgToEmailNode implements TbNode { } } - private TbMsg buildEmailMsg(TbMsg msg, EmailPojo email) throws JsonProcessingException { + private TbMsg buildEmailMsg(TbContext ctx, TbMsg msg, EmailPojo email) throws JsonProcessingException { String emailJson = MAPPER.writeValueAsString(email); - return new TbMsg(UUIDs.timeBased(), SEND_EMAIL_TYPE, msg.getOriginator(), msg.getMetaData().copy(), emailJson); + return ctx.transformMsg(msg, SEND_EMAIL_TYPE, msg.getOriginator(), msg.getMetaData().copy(), emailJson); } private EmailPojo convert(TbMsg msg) throws IOException { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java index 407476b5ae..0b74d6efa7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbSendEmailNode.java @@ -63,8 +63,6 @@ public class TbSendEmailNode implements TbNode { } catch (Exception ex) { ctx.tellError(msg, ex); } - - } private EmailPojo getEmail(TbMsg msg) throws IOException { @@ -84,6 +82,5 @@ public class TbSendEmailNode implements TbNode { @Override public void destroy() { - } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java new file mode 100644 index 0000000000..0ceda9c250 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -0,0 +1,144 @@ +/** + * 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.rule.engine.mqtt; + +import io.netty.buffer.Unpooled; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.codec.mqtt.MqttQoS; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.util.concurrent.Future; +import lombok.extern.slf4j.Slf4j; +import nl.jk5.mqtt.MqttClient; +import nl.jk5.mqtt.MqttClientConfig; +import nl.jk5.mqtt.MqttConnectResult; +import org.springframework.util.StringUtils; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import javax.net.ssl.SSLException; +import java.nio.charset.Charset; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "mqtt", + configClazz = TbMqttNodeConfiguration.class, + nodeDescription = "Publish messages to MQTT broker", + nodeDetails = "Expects messages with any message type. Will publish message to MQTT broker.", + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + configDirective = "tbActionNodeMqttConfig" +) +public class TbMqttNode implements TbNode { + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private static final String ERROR = "error"; + + private TbMqttNodeConfiguration config; + + private EventLoopGroup eventLoopGroup; + private MqttClient mqttClient; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + try { + this.config = TbNodeUtils.convert(configuration, TbMqttNodeConfiguration.class); + this.eventLoopGroup = new NioEventLoopGroup(); + this.mqttClient = initClient(); + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { + String topic = TbNodeUtils.processPattern(this.config.getTopicPattern(), msg.getMetaData()); + this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) + .addListener(future -> { + if (future.isSuccess()) { + TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData()); + ctx.tellNext(next, TbRelationTypes.SUCCESS); + } else { + TbMsg next = processException(ctx, msg, future.cause()); + ctx.tellNext(next, TbRelationTypes.FAILURE, future.cause()); + } + } + ); + } + + private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + @Override + public void destroy() { + if (this.mqttClient != null) { + this.mqttClient.disconnect(); + } + if (this.eventLoopGroup != null) { + this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS); + } + } + + private MqttClient initClient() throws Exception { + Optional sslContextOpt = initSslContext(); + MqttClientConfig config = sslContextOpt.isPresent() ? new MqttClientConfig(sslContextOpt.get()) : new MqttClientConfig(); + if (!StringUtils.isEmpty(this.config.getClientId())) { + config.setClientId(this.config.getClientId()); + } + this.config.getCredentials().configure(config); + MqttClient client = MqttClient.create(config); + client.setEventLoop(this.eventLoopGroup); + Future connectFuture = client.connect(this.config.getHost(), this.config.getPort()); + MqttConnectResult result; + try { + result = connectFuture.get(this.config.getConnectTimeoutSec(), TimeUnit.SECONDS); + } catch (TimeoutException ex) { + connectFuture.cancel(true); + client.disconnect(); + String hostPort = this.config.getHost() + ":" + this.config.getPort(); + throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort)); + } + if (!result.isSuccess()) { + connectFuture.cancel(true); + client.disconnect(); + String hostPort = this.config.getHost() + ":" + this.config.getPort(); + throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode())); + } + return client; + } + + private Optional initSslContext() throws SSLException { + Optional result = this.config.getCredentials().initSslContext(); + if (this.config.isSsl() && !result.isPresent()) { + result = Optional.of(SslContextBuilder.forClient().build()); + } + return result; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java new file mode 100644 index 0000000000..f63e20143e --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java @@ -0,0 +1,48 @@ +/** + * 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.rule.engine.mqtt; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.rule.engine.mqtt.credentials.AnonymousCredentials; +import org.thingsboard.rule.engine.mqtt.credentials.MqttClientCredentials; + +@Data +public class TbMqttNodeConfiguration implements NodeConfiguration { + + private String topicPattern; + private String host; + private int port; + private int connectTimeoutSec; + private String clientId; + + private boolean ssl; + private MqttClientCredentials credentials; + + @Override + public TbMqttNodeConfiguration defaultConfiguration() { + TbMqttNodeConfiguration configuration = new TbMqttNodeConfiguration(); + configuration.setTopicPattern("my-topic"); + configuration.setHost("localhost"); + configuration.setPort(1883); + configuration.setConnectTimeoutSec(10); + configuration.setSsl(false); + configuration.setCredentials(new AnonymousCredentials()); + return configuration; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java new file mode 100644 index 0000000000..9d5e8df9b5 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/AnonymousCredentials.java @@ -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.rule.engine.mqtt.credentials; + +import io.netty.handler.ssl.SslContext; +import nl.jk5.mqtt.MqttClientConfig; + +import java.util.Optional; + +public class AnonymousCredentials implements MqttClientCredentials { + + @Override + public Optional initSslContext() { + return Optional.empty(); + } + + @Override + public void configure(MqttClientConfig config) { + + } +} + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java new file mode 100644 index 0000000000..cbbd7036a2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/BasicCredentials.java @@ -0,0 +1,43 @@ +/** + * 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.rule.engine.mqtt.credentials; + +import io.netty.handler.ssl.SslContext; +import lombok.Data; +import nl.jk5.mqtt.MqttClientConfig; + +import java.util.Optional; + +@Data +public class BasicCredentials implements MqttClientCredentials { + + private String username; + private String password; + + @Override + public Optional initSslContext() { + return Optional.empty(); + } + + @Override + public void configure(MqttClientConfig config) { + config.setUsername(username); + config.setPassword(password); + } + +} + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java new file mode 100644 index 0000000000..c9fb4a32a9 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/CertPemClientCredentials.java @@ -0,0 +1,154 @@ +/** + * 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.rule.engine.mqtt.credentials; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import io.netty.handler.ssl.ClientAuth; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import nl.jk5.mqtt.MqttClientConfig; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMDecryptorProvider; +import org.bouncycastle.openssl.PEMEncryptedKeyPair; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder; +import org.springframework.util.StringUtils; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; +import java.io.ByteArrayInputStream; +import java.security.*; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Optional; + +@Data +@Slf4j +@JsonIgnoreProperties(ignoreUnknown = true) +public class CertPemClientCredentials implements MqttClientCredentials { + + private static final String TLS_VERSION = "TLSv1.2"; + + private String caCert; + private String cert; + private String privateKey; + private String password; + + @Override + public Optional initSslContext() { + try { + Security.addProvider(new BouncyCastleProvider()); + return Optional.of(SslContextBuilder.forClient() + .keyManager(createAndInitKeyManagerFactory()) + .trustManager(createAndInitTrustManagerFactory()) + .clientAuth(ClientAuth.REQUIRE) + .build()); + } catch (Exception e) { + log.error("[{}:{}] Creating TLS factory failed!", caCert, cert, e); + throw new RuntimeException("Creating TLS factory failed!", e); + } + } + + @Override + public void configure(MqttClientConfig config) { + + } + + private KeyManagerFactory createAndInitKeyManagerFactory() throws Exception { + X509Certificate certHolder = readCertFile(cert); + Object keyObject = readPrivateKeyFile(privateKey); + char[] passwordCharArray = "".toCharArray(); + if (!StringUtils.isEmpty(password)) { + passwordCharArray = password.toCharArray(); + } + + JcaPEMKeyConverter keyConverter = new JcaPEMKeyConverter().setProvider("BC"); + + PrivateKey privateKey; + if (keyObject instanceof PEMEncryptedKeyPair) { + PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder().build(passwordCharArray); + KeyPair key = keyConverter.getKeyPair(((PEMEncryptedKeyPair) keyObject).decryptKeyPair(provider)); + privateKey = key.getPrivate(); + } else if (keyObject instanceof PEMKeyPair) { + KeyPair key = keyConverter.getKeyPair((PEMKeyPair) keyObject); + privateKey = key.getPrivate(); + } else if (keyObject instanceof PrivateKey) { + privateKey = (PrivateKey)keyObject; + } else { + throw new RuntimeException("Unable to get private key from object: " + keyObject.getClass()); + } + + KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + clientKeyStore.load(null, null); + clientKeyStore.setCertificateEntry("cert", certHolder); + clientKeyStore.setKeyEntry("private-key", + privateKey, + passwordCharArray, + new Certificate[]{certHolder}); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + keyManagerFactory.init(clientKeyStore, passwordCharArray); + return keyManagerFactory; + } + + private TrustManagerFactory createAndInitTrustManagerFactory() throws Exception { + X509Certificate caCertHolder; + caCertHolder = readCertFile(caCert); + + KeyStore caKeyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + caKeyStore.load(null, null); + caKeyStore.setCertificateEntry("caCert-cert", caCertHolder); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init(caKeyStore); + return trustManagerFactory; + } + + private X509Certificate readCertFile(String fileContent) throws Exception { + X509Certificate certificate = null; + if (fileContent != null && !fileContent.trim().isEmpty()) { + fileContent = fileContent.replace("-----BEGIN CERTIFICATE-----", "") + .replace("-----END CERTIFICATE-----", "") + .replaceAll("\\s", ""); + byte[] decoded = Base64.decodeBase64(fileContent); + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + certificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decoded)); + } + return certificate; + } + + private PrivateKey readPrivateKeyFile(String fileContent) throws Exception { + RSAPrivateKey privateKey = null; + if (fileContent != null && !fileContent.isEmpty()) { + fileContent = fileContent.replaceAll(".*BEGIN.*PRIVATE KEY.*", "") + .replaceAll(".*END.*PRIVATE KEY.*", "") + .replaceAll("\\s", ""); + byte[] decoded = Base64.decodeBase64(fileContent); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + privateKey = (RSAPrivateKey) keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decoded)); + } + return privateKey; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java new file mode 100644 index 0000000000..5c4594ff44 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/credentials/MqttClientCredentials.java @@ -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.rule.engine.mqtt.credentials; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import io.netty.handler.ssl.SslContext; +import nl.jk5.mqtt.MqttClientConfig; + +import java.util.Optional; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = AnonymousCredentials.class, name = "anonymous"), + @JsonSubTypes.Type(value = BasicCredentials.class, name = "basic"), + @JsonSubTypes.Type(value = CertPemClientCredentials.class, name = "cert.PEM")}) +public interface MqttClientCredentials { + + Optional initSslContext(); + + void configure(MqttClientConfig config); +} + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java new file mode 100644 index 0000000000..c318c58688 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNode.java @@ -0,0 +1,147 @@ +/** + * 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.rule.engine.rabbitmq; + +import com.google.common.util.concurrent.ListenableFuture; +import com.rabbitmq.client.*; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.nio.charset.Charset; +import java.util.concurrent.ExecutionException; + +import static org.thingsboard.rule.engine.DonAsynchron.withCallback; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "rabbitmq", + configClazz = TbRabbitMqNodeConfiguration.class, + nodeDescription = "Publish messages to RabbitMQ", + nodeDetails = "Expects messages with any message type. Will publish message to RabbitMQ queue.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeRabbitMqConfig" +) +public class TbRabbitMqNode implements TbNode { + + private static final Charset UTF8 = Charset.forName("UTF-8"); + + private static final String ERROR = "error"; + + private TbRabbitMqNodeConfiguration config; + + private Connection connection; + private Channel channel; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbRabbitMqNodeConfiguration.class); + ConnectionFactory factory = new ConnectionFactory(); + factory.setHost(this.config.getHost()); + factory.setPort(this.config.getPort()); + factory.setVirtualHost(this.config.getVirtualHost()); + factory.setUsername(this.config.getUsername()); + factory.setPassword(this.config.getPassword()); + factory.setAutomaticRecoveryEnabled(this.config.isAutomaticRecoveryEnabled()); + factory.setConnectionTimeout(this.config.getConnectionTimeout()); + factory.setHandshakeTimeout(this.config.getHandshakeTimeout()); + this.config.getClientProperties().forEach((k,v) -> factory.getClientProperties().put(k,v)); + try { + this.connection = factory.newConnection(); + this.channel = this.connection.createChannel(); + } catch (Exception e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { + withCallback(publishMessageAsync(ctx, msg), + m -> ctx.tellNext(m, TbRelationTypes.SUCCESS), + t -> { + TbMsg next = processException(ctx, msg, t); + ctx.tellNext(next, TbRelationTypes.FAILURE, t); + }); + } + + ListenableFuture publishMessageAsync(TbContext ctx, TbMsg msg) { + return ctx.getExternalCallExecutor().executeAsync(() -> publishMessage(ctx, msg)); + } + + TbMsg publishMessage(TbContext ctx, TbMsg msg) throws Exception { + String exchangeName = ""; + if (!StringUtils.isEmpty(this.config.getExchangeNamePattern())) { + exchangeName = TbNodeUtils.processPattern(this.config.getExchangeNamePattern(), msg.getMetaData()); + } + String routingKey = ""; + if (!StringUtils.isEmpty(this.config.getRoutingKeyPattern())) { + routingKey = TbNodeUtils.processPattern(this.config.getRoutingKeyPattern(), msg.getMetaData()); + } + AMQP.BasicProperties properties = null; + if (!StringUtils.isEmpty(this.config.getMessageProperties())) { + properties = convert(this.config.getMessageProperties()); + } + channel.basicPublish( + exchangeName, + routingKey, + properties, + msg.getData().getBytes(UTF8)); + return ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData()); + } + + private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable t) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(ERROR, t.getClass() + ": " + t.getMessage()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + @Override + public void destroy() { + if (this.connection != null) { + try { + this.connection.close(); + } catch (Exception e) { + log.error("Failed to close connection during destroy()", e); + } + } + } + + private static AMQP.BasicProperties convert(String name) throws TbNodeException { + switch (name) { + case "BASIC": + return MessageProperties.BASIC; + case "TEXT_PLAIN": + return MessageProperties.TEXT_PLAIN; + case "MINIMAL_BASIC": + return MessageProperties.MINIMAL_BASIC; + case "MINIMAL_PERSISTENT_BASIC": + return MessageProperties.MINIMAL_PERSISTENT_BASIC; + case "PERSISTENT_BASIC": + return MessageProperties.PERSISTENT_BASIC; + case "PERSISTENT_TEXT_PLAIN": + return MessageProperties.PERSISTENT_TEXT_PLAIN; + default: + throw new TbNodeException("Message Properties: '" + name + "' is undefined!"); + } + } +} + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java new file mode 100644 index 0000000000..1a0a00a049 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rabbitmq/TbRabbitMqNodeConfiguration.java @@ -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.rule.engine.rabbitmq; + +import com.rabbitmq.client.ConnectionFactory; +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +import java.util.Collections; +import java.util.Map; + +@Data +public class TbRabbitMqNodeConfiguration implements NodeConfiguration { + + private String exchangeNamePattern; + private String routingKeyPattern; + private String messageProperties; + private String host; + private int port; + private String virtualHost; + private String username; + private String password; + private boolean automaticRecoveryEnabled; + private int connectionTimeout; + private int handshakeTimeout; + private Map clientProperties; + + @Override + public TbRabbitMqNodeConfiguration defaultConfiguration() { + TbRabbitMqNodeConfiguration configuration = new TbRabbitMqNodeConfiguration(); + configuration.setExchangeNamePattern(""); + configuration.setRoutingKeyPattern(""); + configuration.setMessageProperties(null); + configuration.setHost(ConnectionFactory.DEFAULT_HOST); + configuration.setPort(ConnectionFactory.DEFAULT_AMQP_PORT); + configuration.setVirtualHost(ConnectionFactory.DEFAULT_VHOST); + configuration.setUsername(ConnectionFactory.DEFAULT_USER); + configuration.setPassword(ConnectionFactory.DEFAULT_PASS); + configuration.setAutomaticRecoveryEnabled(false); + configuration.setConnectionTimeout(ConnectionFactory.DEFAULT_CONNECTION_TIMEOUT); + configuration.setHandshakeTimeout(ConnectionFactory.DEFAULT_HANDSHAKE_TIMEOUT); + configuration.setClientProperties(Collections.emptyMap()); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java new file mode 100644 index 0000000000..311f81dfe4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -0,0 +1,152 @@ +/** + * 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.rule.engine.rest; + +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.ssl.SslContextBuilder; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.Netty4ClientHttpRequestFactory; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.ListenableFutureCallback; +import org.springframework.web.client.*; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import javax.net.ssl.SSLException; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "rest api call", + configClazz = TbRestApiCallNodeConfiguration.class, + nodeDescription = "Invoke REST API calls to external REST server", + nodeDetails = "Expects messages with any message type. Will invoke REST API call to external REST server.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeRestApiCallConfig" +) +public class TbRestApiCallNode implements TbNode { + + private static final String STATUS = "status"; + private static final String STATUS_CODE = "statusCode"; + private static final String STATUS_REASON = "statusReason"; + private static final String ERROR = "error"; + private static final String ERROR_BODY = "error_body"; + + private TbRestApiCallNodeConfiguration config; + + private EventLoopGroup eventLoopGroup; + private AsyncRestTemplate httpClient; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + try { + this.config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class); + this.eventLoopGroup = new NioEventLoopGroup(); + Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup); + nettyFactory.setSslContext(SslContextBuilder.forClient().build()); + httpClient = new AsyncRestTemplate(nettyFactory); + } catch (SSLException e) { + throw new TbNodeException(e); + } + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { + String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg.getMetaData()); + HttpHeaders headers = prepareHeaders(msg.getMetaData()); + HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); + HttpEntity entity = new HttpEntity<>(msg.getData(), headers); + + ListenableFuture> future = httpClient.exchange( + endpointUrl, method, entity, String.class); + + future.addCallback(new ListenableFutureCallback>() { + @Override + public void onFailure(Throwable throwable) { + TbMsg next = processException(ctx, msg, throwable); + ctx.tellNext(next, TbRelationTypes.FAILURE, throwable); + } + + @Override + public void onSuccess(ResponseEntity responseEntity) { + if (responseEntity.getStatusCode().is2xxSuccessful()) { + TbMsg next = processResponse(ctx, msg, responseEntity); + ctx.tellNext(next, TbRelationTypes.SUCCESS); + } else { + TbMsg next = processFailureResponse(ctx, msg, responseEntity); + ctx.tellNext(next, TbRelationTypes.FAILURE); + } + } + }); + } + + @Override + public void destroy() { + if (this.eventLoopGroup != null) { + this.eventLoopGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS); + } + } + + private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { + TbMsgMetaData metaData = new TbMsgMetaData(); + metaData.putValue(STATUS, response.getStatusCode().name()); + metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); + metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); + response.getHeaders().toSingleValueMap().forEach((k,v) -> metaData.putValue(k,v) ); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody()); + } + + private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(STATUS, response.getStatusCode().name()); + metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); + metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); + metaData.putValue(ERROR_BODY, response.getBody()); + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { + TbMsgMetaData metaData = origMsg.getMetaData().copy(); + metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); + if (e instanceof HttpClientErrorException) { + HttpClientErrorException httpClientErrorException = (HttpClientErrorException)e; + metaData.putValue(STATUS, httpClientErrorException.getStatusText()); + metaData.putValue(STATUS_CODE, httpClientErrorException.getRawStatusCode()+""); + metaData.putValue(ERROR_BODY, httpClientErrorException.getResponseBodyAsString()); + } + return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); + } + + private HttpHeaders prepareHeaders(TbMsgMetaData metaData) { + HttpHeaders headers = new HttpHeaders(); + config.getHeaders().forEach((k,v) -> { + headers.add(TbNodeUtils.processPattern(k, metaData), TbNodeUtils.processPattern(v, metaData)); + }); + return headers; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java new file mode 100644 index 0000000000..812eb776fd --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -0,0 +1,39 @@ +/** + * 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.rule.engine.rest; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +import java.util.Collections; +import java.util.Map; + +@Data +public class TbRestApiCallNodeConfiguration implements NodeConfiguration { + + private String restEndpointUrlPattern; + private String requestMethod; + private Map headers; + + @Override + public TbRestApiCallNodeConfiguration defaultConfiguration() { + TbRestApiCallNodeConfiguration configuration = new TbRestApiCallNodeConfiguration(); + configuration.setRestEndpointUrlPattern("http://localhost/api"); + configuration.setRequestMethod("POST"); + configuration.setHeaders(Collections.emptyMap()); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java new file mode 100644 index 0000000000..ccaac49cee --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java @@ -0,0 +1,68 @@ +/** + * 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.rule.engine.rpc; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "rpc call reply", + configClazz = TbSendRpcReplyNodeConfiguration.class, + nodeDescription = "Sends reply to the RPC call from device", + nodeDetails = "Expects messages with any message type. Will forward message body to the device.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeRpcReplyConfig" +) +public class TbSendRPCReplyNode implements TbNode { + + private TbSendRpcReplyNodeConfiguration config; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbSendRpcReplyNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + String requestIdStr = msg.getMetaData().getValue(config.getRequestIdMetaDataAttribute()); + if (msg.getOriginator().getEntityType() != EntityType.DEVICE) { + ctx.tellError(msg, new RuntimeException("Message originator is not a device entity!")); + } else if (StringUtils.isEmpty(requestIdStr)) { + ctx.tellError(msg, new RuntimeException("Request id is not present in the metadata!")); + } else if (StringUtils.isEmpty(msg.getData())) { + ctx.tellError(msg, new RuntimeException("Request body is empty!")); + } else { + ctx.getRpcService().sendRpcReply(new DeviceId(msg.getOriginator().getId()), Integer.parseInt(requestIdStr), msg.getData()); + } + } + + @Override + public void destroy() { + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java new file mode 100644 index 0000000000..3d386533b3 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -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. + */ +package org.thingsboard.rule.engine.rpc; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.rule.engine.api.TbRelationTypes; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "rpc call request", + configClazz = TbSendRpcRequestNodeConfiguration.class, + nodeDescription = "Sends one-way RPC call to device", + nodeDetails = "Expects messages with \"method\" and \"params\". Will forward response from device to next nodes.", + uiResources = {"static/rulenode/rulenode-core-config.js"}, + configDirective = "tbActionNodeRpcRequestConfig" +) +public class TbSendRPCRequestNode implements TbNode { + + private Random random = new Random(); + private Gson gson = new Gson(); + private JsonParser jsonParser = new JsonParser(); + private TbSendRpcRequestNodeConfiguration config; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbSendRpcRequestNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + JsonObject json = jsonParser.parse(msg.getData()).getAsJsonObject(); + + if (msg.getOriginator().getEntityType() != EntityType.DEVICE) { + ctx.tellError(msg, new RuntimeException("Message originator is not a device entity!")); + } else if (!json.has("method")) { + ctx.tellError(msg, new RuntimeException("Method is not present in the message!")); + } else if (!json.has("params")) { + ctx.tellError(msg, new RuntimeException("Params are not present in the message!")); + } else { + int requestId = json.has("requestId") ? json.get("requestId").getAsInt() : random.nextInt(); + RuleEngineDeviceRpcRequest request = RuleEngineDeviceRpcRequest.builder() + .method(gson.toJson(json.get("method"))) + .body(gson.toJson(json.get("params"))) + .deviceId(new DeviceId(msg.getOriginator().getId())) + .requestId(requestId) + .timeout(TimeUnit.SECONDS.toMillis(config.getTimeoutInSeconds())) + .build(); + + ctx.getRpcService().sendRpcRequest(request, ruleEngineDeviceRpcResponse -> { + if (!ruleEngineDeviceRpcResponse.getError().isPresent()) { + TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().get()); + ctx.tellNext(next, TbRelationTypes.SUCCESS); + } else { + TbMsg next = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); + ctx.tellNext(next, TbRelationTypes.FAILURE); + ctx.tellError(msg, new RuntimeException(ruleEngineDeviceRpcResponse.getError().get().name())); + } + }); + } + } + + @Override + public void destroy() { + } + + private String wrap(String name, String body) { + JsonObject json = new JsonObject(); + json.addProperty(name, body); + return gson.toJson(json); + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java new file mode 100644 index 0000000000..402a33bc15 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcReplyNodeConfiguration.java @@ -0,0 +1,33 @@ +/** + * 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.rule.engine.rpc; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.DataConstants; + +@Data +public class TbSendRpcReplyNodeConfiguration implements NodeConfiguration { + + private String requestIdMetaDataAttribute; + + @Override + public TbSendRpcReplyNodeConfiguration defaultConfiguration() { + TbSendRpcReplyNodeConfiguration configuration = new TbSendRpcReplyNodeConfiguration(); + configuration.setRequestIdMetaDataAttribute("requestId"); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcRequestNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcRequestNodeConfiguration.java new file mode 100644 index 0000000000..214ce659a9 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRpcRequestNodeConfiguration.java @@ -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.rule.engine.rpc; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; + +@Data +public class TbSendRpcRequestNodeConfiguration implements NodeConfiguration { + + private int timeoutInSeconds; + + @Override + public TbSendRpcRequestNodeConfiguration defaultConfiguration() { + TbSendRpcRequestNodeConfiguration configuration = new TbSendRpcRequestNodeConfiguration(); + configuration.setTimeoutInSeconds(60); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java new file mode 100644 index 0000000000..8ab57c7bb2 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -0,0 +1,78 @@ +/** + * 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.rule.engine.telemetry; + +import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.StringUtils; +import org.thingsboard.rule.engine.TbNodeUtils; +import org.thingsboard.rule.engine.api.RuleNode; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.core.AttributesUpdateRequest; +import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +@Slf4j +@RuleNode( + type = ComponentType.ACTION, + name = "save attributes", + configClazz = TbMsgAttributesNodeConfiguration.class, + nodeDescription = "Saves attributes data", + nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type", + uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, + configDirective = "tbActionNodeAttributesConfig" +) +public class TbMsgAttributesNode implements TbNode { + + private TbMsgAttributesNodeConfiguration config; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + this.config = TbNodeUtils.convert(configuration, TbMsgAttributesNodeConfiguration.class); + } + + @Override + public void onMsg(TbContext ctx, TbMsg msg) { + if (!msg.getType().equals(SessionMsgType.POST_ATTRIBUTES_REQUEST.name())) { + ctx.tellError(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); + return; + } + + String src = msg.getData(); + Set attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes(); + ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg)); + } + + @Override + public void destroy() { + } + +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java new file mode 100644 index 0000000000..c6339176cb --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java @@ -0,0 +1,33 @@ +/** + * 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.rule.engine.telemetry; + +import lombok.Data; +import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.DataConstants; + +@Data +public class TbMsgAttributesNodeConfiguration implements NodeConfiguration { + + private String scope; + + @Override + public TbMsgAttributesNodeConfiguration defaultConfiguration() { + TbMsgAttributesNodeConfiguration configuration = new TbMsgAttributesNodeConfiguration(); + configuration.setScope(DataConstants.SERVER_SCOPE); + return configuration; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java similarity index 86% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTelemetryNode.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index 0e6687fabf..518620be2e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -30,9 +30,9 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.core.TelemetryUploadRequest; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -40,26 +40,25 @@ import java.util.Map; @Slf4j @RuleNode( type = ComponentType.ACTION, - name = "save timeseries data", - configClazz = TbMsgTelemetryNodeConfiguration.class, + name = "save timeseries", + configClazz = TbMsgTimeseriesNodeConfiguration.class, nodeDescription = "Saves timeseries data", - nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY' message type", + nodeDetails = "Saves timeseries telemetry data based on configurable TTL parameter. Expects messages with 'POST_TELEMETRY_REQUEST' message type", uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"}, - configDirective = "tbActionNodeTelemetryConfig" + configDirective = "tbActionNodeTimeseriesConfig" ) +public class TbMsgTimeseriesNode implements TbNode { -public class TbMsgTelemetryNode implements TbNode { - - private TbMsgTelemetryNodeConfiguration config; + private TbMsgTimeseriesNodeConfiguration config; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { - this.config = TbNodeUtils.convert(configuration, TbMsgTelemetryNodeConfiguration.class); + this.config = TbNodeUtils.convert(configuration, TbMsgTimeseriesNodeConfiguration.class); } @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (!msg.getType().equals("POST_TELEMETRY")) { + if (!msg.getType().equals(SessionMsgType.POST_TELEMETRY_REQUEST.name())) { ctx.tellError(msg, new IllegalArgumentException("Unsupported msg type: " + msg.getType())); return; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTelemetryNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeConfiguration.java similarity index 74% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTelemetryNodeConfiguration.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeConfiguration.java index 5523926f76..d405bdcd17 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTelemetryNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNodeConfiguration.java @@ -18,16 +18,14 @@ package org.thingsboard.rule.engine.telemetry; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; -import java.util.Map; - @Data -public class TbMsgTelemetryNodeConfiguration implements NodeConfiguration { +public class TbMsgTimeseriesNodeConfiguration implements NodeConfiguration { private long defaultTTL; @Override - public TbMsgTelemetryNodeConfiguration defaultConfiguration() { - TbMsgTelemetryNodeConfiguration configuration = new TbMsgTelemetryNodeConfiguration(); + public TbMsgTimeseriesNodeConfiguration defaultConfiguration() { + TbMsgTimeseriesNodeConfiguration configuration = new TbMsgTimeseriesNodeConfiguration(); configuration.setDefaultTTL(0L); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index 05ad49c144..c221e03d5b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -20,7 +20,6 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.thingsboard.rule.engine.TbNodeUtils; import org.thingsboard.rule.engine.api.*; import org.thingsboard.rule.engine.util.EntitiesCustomerIdAsyncLoader; @@ -60,7 +59,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { @Override protected ListenableFuture transform(TbContext ctx, TbMsg msg) { ListenableFuture newOriginator = getNewOriginator(ctx, msg.getOriginator()); - return Futures.transform(newOriginator, (Function) n -> new TbMsg(msg.getId(), msg.getType(), n, msg.getMetaData(), msg.getData())); + return Futures.transform(newOriginator, (Function) n -> ctx.transformMsg(msg, msg.getType(), n, msg.getMetaData(), msg.getData())); } private ListenableFuture getNewOriginator(TbContext ctx, EntityId original) { diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css index b1728b184f..b5109b5f90 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.css @@ -1,2 +1,2 @@ -.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-generator-config tb-json-content.tb-message-body,.tb-generator-config tb-json-object-edit.tb-metadata-json{height:200px;display:block}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0} +.tb-message-type-autocomplete .tb-not-found{display:block;line-height:1.5;height:48px}.tb-message-type-autocomplete .tb-not-found .tb-no-entries{line-height:48px}.tb-message-type-autocomplete li{height:auto!important;white-space:normal!important}.tb-generator-config tb-json-content.tb-message-body,.tb-generator-config tb-json-object-edit.tb-metadata-json{height:200px;display:block}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:90px}@media (min-width:960px){.tb-mqtt-config .tb-credentials-panel-group .tb-panel-title{min-width:180px}}.tb-mqtt-config .tb-credentials-panel-group .tb-panel-prompt{font-size:14px;color:rgba(0,0,0,.87);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-prompt,.tb-mqtt-config .tb-credentials-panel-group.disabled .tb-panel-title{color:rgba(0,0,0,.38)}.tb-mqtt-config .tb-credentials-panel-group md-icon.md-expansion-panel-icon{margin-right:0}.tb-mqtt-config .tb-container{width:100%}.tb-mqtt-config .dropdown-messages .tb-error-message{padding:5px 0 0}.tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}.tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}.tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}.tb-kv-map-config .body .row{padding-top:5px;max-height:40px}.tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}.tb-kv-map-config .body md-input-container.cell{margin:0;max-height:40px}.tb-kv-map-config .body .md-button{margin:0} /*# sourceMappingURL=rulenode-core-config.css.map*/ \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index edf7ddcff8..ec7374c9a7 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,3 +1,4 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(40)},function(e,t){},1,1,function(e,t){e.exports="
{{ 'tb.rulenode.test-condition-function' | translate }}
{{ 'tb.rulenode.test-condition-function' | translate }}
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},8,function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
{{ \'tb.rulenode.clone-message\' | translate }}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
{{ 'tb.rulenode.clone-message' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.to-template-required
tb.rulenode.subject-template-required
tb.rulenode.body-template-required
"},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testConditionJs=function(e,n){var i=angular.copy(n?a.configuration.createConditionJs:a.configuration.clearConditionJs),o={temperature:22.4,humidity:78},l={sensorType:"temperature"};r.testNodeScript(e,i,"filter",t.instant("tb.rulenode.condition")+"",n?"isAlarm":"isCleared",["msg","metadata","msgType"],o,l,"POST_TELEMETRY").then(function(e){n?a.configuration.createConditionJs=e:a.configuration.clearConditionJs=e,s.$setDirty()})},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(4),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(24),i=r(a),o=n(21),l=r(o),s=n(20),u=r(s),d=n(23),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTelemetryConfig",i.default).directive("tbActionNodeGeneratorConfig",l.default).directive("tbActionNodeAlarmConfig",u.default).directive("tbActionNodeLogConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(27),i=r(a),o=n(28),l=r(o),s=n(25),u=r(s),d=n(29),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(10),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(11),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(32),i=r(a),o=n(31),l=r(o),s=n(33),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i);n(3)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(17),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(36),i=r(a),o=n(38),l=r(o),s=n(39),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(18),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{ -default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(43),i=r(a),o=n(30),l=r(o),s=n(26),u=r(s),d=n(37),c=r(d),m=n(22),g=r(m),f=n(35),p=r(f),b=n(34),v=r(b),y=n(42),T=r(y);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbRelationsQueryConfig",p.default).directive("tbKvMapConfig",v.default).config(T.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","to-template":"To Template","to-template-required":"To Template is required","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(41),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES:{name:"Post attributes",value:"POST_ATTRIBUTES"},POST_TELEMETRY:{name:"Post telemetry",value:"POST_TELEMETRY"},RPC_REQUEST:{name:"RPC Request",value:"RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}}}).name}])); +!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(60)},function(e,t){},1,1,1,function(e,t){e.exports="
{{ 'tb.rulenode.test-condition-function' | translate }}
{{ 'tb.rulenode.test-condition-function' | translate }}
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
{{ type }}
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},18,function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"; +},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
{{ \'tb.rulenode.clone-message\' | translate }}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
{{ 'tb.rulenode.clone-message' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.to-template-required
tb.rulenode.subject-template-required
tb.rulenode.body-template-required
"},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testConditionJs=function(e,n){var i=angular.copy(n?a.configuration.createConditionJs:a.configuration.clearConditionJs),o={temperature:22.4,humidity:78},l={sensorType:"temperature"};r.testNodeScript(e,i,"filter",t.instant("tb.rulenode.condition")+"",n?"isAlarm":"isCleared",["msg","metadata","msgType"],o,l,"POST_TELEMETRY").then(function(e){n?a.configuration.createConditionJs=e:a.configuration.clearConditionJs=e,s.$setDirty()})},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],i,o,"DebugMsg").then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(43),i=r(a),o=n(31),l=r(o),s=n(32),u=r(s),d=n(30),c=r(d),m=n(35),g=r(m),p=n(39),f=r(p),b=n(40),v=r(b),y=n(38),q=r(y),T=n(34),h=r(T),$=n(41),k=r($),w=n(42),C=r(w),x=n(37),_=r(x),M=n(36),S=r(M);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeAlarmConfig",c.default).directive("tbActionNodeLogConfig",g.default).directive("tbActionNodeRpcReplyConfig",f.default).directive("tbActionNodeRpcRequestConfig",v.default).directive("tbActionNodeRestApiCallConfig",q.default).directive("tbActionNodeKafkaConfig",h.default).directive("tbActionNodeSnsConfig",k.default).directive("tbActionNodeSqsConfig",C.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",S.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$mdExpansionPanel=t,r.ruleNodeTypes=n,r.credentialsTypeChanged=function(){var e=r.configuration.credentials.type;r.configuration.credentials={},r.configuration.credentials.type=e,r.updateValidity()},r.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){r.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(r.configuration.credentials.caCertFileName=e.name,r.configuration.credentials.caCert=a),"privateKey"==t&&(r.configuration.credentials.privateKeyFileName=e.name,r.configuration.credentials.privateKey=a),"Cert"==t&&(r.configuration.credentials.certFileName=e.name,r.configuration.credentials.cert=a)),r.updateValidity()}})},n.readAsText(e.file)},r.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(r.configuration.credentials.caCertFileName=null,r.configuration.credentials.caCert=null),"privateKey"==e&&(r.configuration.credentials.privateKeyFileName=null,r.configuration.credentials.privateKey=null),"Cert"==e&&(r.configuration.credentials.certFileName=null,r.configuration.credentials.cert=null),r.updateValidity()},r.updateValidity=function(){var e=!0,t=r.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var i=n(10),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(11),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(17),o=r(i)},function(e,t){"use strict";function n(e){var t=function(t,n,r,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(18),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(47),i=r(a),o=n(48),l=r(o),s=n(45),u=r(s),d=n(49),c=r(d);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeRelatedAttributesConfig",l.default).directive("tbEnrichmentNodeCustomerAttributesConfig",u.default).directive("tbEnrichmentNodeTenantAttributesConfig",c.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(20),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(21),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(52),i=r(a),o=n(51),l=r(o),s=n(53),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(25),o=r(i);n(4)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(26),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(27),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(56),i=r(a),o=n(58),l=r(o),s=n(59),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript),i={temperature:22.4,humidity:78},o={sensorType:"temperature"};n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],i,o,"POST_TELEMETRY").then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(28),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(29),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(63),i=r(a),o=n(50),l=r(o),s=n(46),u=r(s),d=n(57),c=r(d),m=n(33),g=r(m),p=n(44),f=r(p),b=n(55),v=r(b),y=n(54),q=r(y),T=n(62),h=r(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbKvMapConfig",q.default).config(h.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","to-template":"To Template","to-template-required":"To Template is required","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","request-method":"Request method",headers:"Headers",header:"Header","header-required":"Header is required", +value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){(0,o.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}a.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(61),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES_REQUEST:{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},POST_TELEMETRY_REQUEST:{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},TO_SERVER_RPC_REQUEST:{name:"RPC Request",value:"TO_SERVER_RPC_REQUEST"}},originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 69c3aca2d2..39a18901f6 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import org.apache.commons.lang3.NotImplementedException; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -30,6 +31,8 @@ import org.thingsboard.rule.engine.api.*; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.DeviceId; 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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -66,16 +69,40 @@ public class TbAlarmNodeTest { @Mock private ScriptEngine detailsJs; + private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + + private ListeningExecutor dbExecutor; + private EntityId originator = new DeviceId(UUIDs.timeBased()); private TenantId tenantId = new TenantId(UUIDs.timeBased()); private TbMsgMetaData metaData = new TbMsgMetaData(); private String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; + @Before + public void before() { + dbExecutor = new ListeningExecutor() { + @Override + public ListenableFuture executeAsync(Callable task) { + try { + return Futures.immediateFuture(task.call()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void execute(Runnable command) { + command.run(); + } + }; + } + @Test public void newAlarmCanBeCreated() throws ScriptException, IOException { initWithScript(); metaData.putValue("key", "value"); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); when(createJs.executeFilter(msg)).thenReturn(true); when(detailsJs.executeJson(msg)).thenReturn(null); @@ -85,17 +112,22 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); - ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx).tellNext(captor.capture(), eq("Created")); - TbMsg actualMsg = captor.getValue(); + verify(ctx).tellNext(any(), eq("Created")); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", actualMsg.getType()); - assertEquals(originator, actualMsg.getOriginator()); - assertEquals("value", actualMsg.getMetaData().getValue("key")); - assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_NEW_ALARM)); - assertNotSame(metaData, actualMsg.getMetaData()); + assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals("value", metadataCaptor.getValue().getValue("key")); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertNotSame(metaData, metadataCaptor.getValue()); - Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class); + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) @@ -115,7 +147,7 @@ public class TbAlarmNodeTest { public void shouldCreateScriptThrowsException() throws ScriptException { initWithScript(); metaData.putValue("key", "value"); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); when(createJs.executeFilter(msg)).thenThrow(new NotImplementedException("message")); @@ -128,6 +160,7 @@ public class TbAlarmNodeTest { verify(ctx).createJsScriptEngine("CLEAR", "isCleared"); verify(ctx).createJsScriptEngine("DETAILS", "Details"); verify(ctx).getJsExecutor(); + verify(ctx).getDbCallbackExecutor(); verifyNoMoreInteractions(ctx, alarmService, clearJs, detailsJs); } @@ -136,7 +169,7 @@ public class TbAlarmNodeTest { public void buildDetailsThrowsException() throws ScriptException, IOException { initWithScript(); metaData.putValue("key", "value"); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); when(createJs.executeFilter(msg)).thenReturn(true); when(detailsJs.executeJson(msg)).thenThrow(new NotImplementedException("message")); @@ -151,6 +184,7 @@ public class TbAlarmNodeTest { verify(ctx).createJsScriptEngine("DETAILS", "Details"); verify(ctx, times(2)).getJsExecutor(); verify(ctx).getAlarmService(); + verify(ctx, times(3)).getDbCallbackExecutor(); verify(ctx).getTenantId(); verify(alarmService).findLatestByOriginatorAndType(tenantId, originator, "SomeType"); @@ -161,7 +195,7 @@ public class TbAlarmNodeTest { public void ifAlarmClearedCreateNew() throws ScriptException, IOException { initWithScript(); metaData.putValue("key", "value"); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); Alarm clearedAlarm = Alarm.builder().status(CLEARED_ACK).build(); @@ -173,17 +207,23 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); - ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx).tellNext(captor.capture(), eq("Created")); - TbMsg actualMsg = captor.getValue(); + verify(ctx).tellNext(any(), eq("Created")); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", actualMsg.getType()); - assertEquals(originator, actualMsg.getOriginator()); - assertEquals("value", actualMsg.getMetaData().getValue("key")); - assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_NEW_ALARM)); - assertNotSame(metaData, actualMsg.getMetaData()); + assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals("value", metadataCaptor.getValue().getValue("key")); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertNotSame(metaData, metadataCaptor.getValue()); - Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class); + + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) @@ -203,7 +243,7 @@ public class TbAlarmNodeTest { public void alarmCanBeUpdated() throws ScriptException, IOException { initWithScript(); metaData.putValue("key", "value"); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); long oldEndDate = System.currentTimeMillis(); Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build(); @@ -217,17 +257,22 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); - ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx).tellNext(captor.capture(), eq("Updated")); - TbMsg actualMsg = captor.getValue(); + verify(ctx).tellNext(any(), eq("Updated")); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", actualMsg.getType()); - assertEquals(originator, actualMsg.getOriginator()); - assertEquals("value", actualMsg.getMetaData().getValue("key")); - assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_EXISTING_ALARM)); - assertNotSame(metaData, actualMsg.getMetaData()); + assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals("value", metadataCaptor.getValue().getValue("key")); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_EXISTING_ALARM)); + assertNotSame(metaData, metadataCaptor.getValue()); - Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class); + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class); assertTrue(activeAlarm.getEndTs() > oldEndDate); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) @@ -249,7 +294,7 @@ public class TbAlarmNodeTest { public void alarmCanBeCleared() throws ScriptException, IOException { initWithScript(); metaData.putValue("key", "value"); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); long oldEndDate = System.currentTimeMillis(); Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).status(ACTIVE_UNACK).severity(WARNING).endTs(oldEndDate).build(); @@ -263,17 +308,22 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); - ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx).tellNext(captor.capture(), eq("Cleared")); - TbMsg actualMsg = captor.getValue(); + verify(ctx).tellNext(any(), eq("Cleared")); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("ALARM", actualMsg.getType()); - assertEquals(originator, actualMsg.getOriginator()); - assertEquals("value", actualMsg.getMetaData().getValue("key")); - assertEquals(Boolean.TRUE.toString(), actualMsg.getMetaData().getValue(IS_CLEARED_ALARM)); - assertNotSame(metaData, actualMsg.getMetaData()); + assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals("value", metadataCaptor.getValue().getValue("key")); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_CLEARED_ALARM)); + assertNotSame(metaData, metadataCaptor.getValue()); - Alarm actualAlarm = new ObjectMapper().readValue(actualMsg.getData().getBytes(), Alarm.class); + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class); Alarm expectedAlarm = Alarm.builder() .tenantId(tenantId) .originator(originator) @@ -307,6 +357,7 @@ public class TbAlarmNodeTest { when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getJsExecutor()).thenReturn(executor); when(ctx.getAlarmService()).thenReturn(alarmService); + when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); mockJsExecutor(); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 08a22f085d..d0b13ebc2e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -27,6 +27,8 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -48,10 +50,13 @@ public class TbJsFilterNodeTest { @Mock private ScriptEngine scriptEngine; + private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + @Test public void falseEvaluationDoNotSendMsg() throws TbNodeException, ScriptException { initWithScript(); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); when(scriptEngine.executeFilter(msg)).thenReturn(false); @@ -64,7 +69,7 @@ public class TbJsFilterNodeTest { public void exceptionInJsThrowsException() throws TbNodeException, ScriptException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); when(scriptEngine.executeFilter(msg)).thenThrow(new ScriptException("error")); @@ -77,7 +82,7 @@ public class TbJsFilterNodeTest { public void metadataConditionCanBeTrue() throws TbNodeException, ScriptException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}"); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); when(scriptEngine.executeFilter(msg)).thenReturn(true); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index a495124afc..cc4c2972ce 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -28,6 +28,8 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -51,6 +53,9 @@ public class TbJsSwitchNodeTest { @Mock private ScriptEngine scriptEngine; + private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + @Test public void multipleRoutesAreAllowed() throws TbNodeException, ScriptException { initWithScript(); @@ -59,7 +64,7 @@ public class TbJsSwitchNodeTest { metaData.putValue("humidity", "99"); String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L); mockJsExecutor(); when(scriptEngine.executeSwitch(msg)).thenReturn(Sets.newHashSet("one", "three")); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java index 877047ce3a..628082760a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java @@ -27,6 +27,8 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.DeviceId; 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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -34,6 +36,7 @@ import java.io.IOException; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; +import static org.mockito.Matchers.any; import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) @@ -48,26 +51,32 @@ public class TbMsgToEmailNodeTest { private TbMsgMetaData metaData = new TbMsgMetaData(); private String rawJson = "{\"name\": \"temp\", \"passed\": 5 , \"complex\": {\"val\":12, \"count\":100}}"; + private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + @Test public void msgCanBeConverted() throws IOException { initWithScript(); metaData.putValue("username", "oreo"); metaData.putValue("userEmail", "user@email.io"); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", originator, metaData, rawJson, ruleChainId, ruleNodeId, 0L); emailNode.onMsg(ctx, msg); - ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx).tellNext(captor.capture()); - TbMsg actualMsg = captor.getValue(); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("SEND_EMAIL", actualMsg.getType()); - assertEquals(originator, actualMsg.getOriginator()); - assertEquals("oreo", actualMsg.getMetaData().getValue("username")); - assertNotSame(metaData, actualMsg.getMetaData()); + assertEquals("SEND_EMAIL", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals("oreo", metadataCaptor.getValue().getValue("username")); + assertNotSame(metaData, metadataCaptor.getValue()); - EmailPojo actual = new ObjectMapper().readValue(actualMsg.getData().getBytes(), EmailPojo.class); + EmailPojo actual = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), EmailPojo.class); EmailPojo expected = new EmailPojo.EmailPojoBuilder() .from("test@mail.org") diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java index e26312b7ca..158b7cd2b9 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetCustomerAttributeNodeTest.java @@ -34,6 +34,8 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.msg.TbMsg; @@ -77,6 +79,9 @@ public class TbGetCustomerAttributeNodeTest { private TbMsg msg; + private RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + private RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + @Before public void init() throws TbNodeException { TbGetEntityAttrNodeConfiguration config = new TbGetEntityAttrNodeConfiguration(); @@ -98,7 +103,8 @@ public class TbGetCustomerAttributeNodeTest { User user = new User(); user.setCustomerId(customerId); - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}"); + + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getUserService()).thenReturn(userService); when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); @@ -123,7 +129,7 @@ public class TbGetCustomerAttributeNodeTest { User user = new User(); user.setCustomerId(customerId); - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}"); + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getUserService()).thenReturn(userService); when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); @@ -148,7 +154,7 @@ public class TbGetCustomerAttributeNodeTest { User user = new User(); user.setCustomerId(customerId); - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}"); + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getUserService()).thenReturn(userService); when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(null)); @@ -166,7 +172,7 @@ public class TbGetCustomerAttributeNodeTest { @Test public void customerAttributeAddedInMetadata() { CustomerId customerId = new CustomerId(UUIDs.timeBased()); - msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), "{}"); + msg = new TbMsg(UUIDs.timeBased(), "CUSTOMER", customerId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); entityAttributeFetched(customerId); } @@ -177,7 +183,7 @@ public class TbGetCustomerAttributeNodeTest { User user = new User(); user.setCustomerId(customerId); - msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}"); + msg = new TbMsg(UUIDs.timeBased(), "USER", userId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getUserService()).thenReturn(userService); when(userService.findUserByIdAsync(userId)).thenReturn(Futures.immediateFuture(user)); @@ -192,7 +198,7 @@ public class TbGetCustomerAttributeNodeTest { Asset asset = new Asset(); asset.setCustomerId(customerId); - msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), "{}"); + msg = new TbMsg(UUIDs.timeBased(), "USER", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); @@ -207,7 +213,7 @@ public class TbGetCustomerAttributeNodeTest { Device device = new Device(); device.setCustomerId(customerId); - msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}"); + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getDeviceService()).thenReturn(deviceService); when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); @@ -234,7 +240,7 @@ public class TbGetCustomerAttributeNodeTest { Device device = new Device(); device.setCustomerId(customerId); - msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}"); + msg = new TbMsg(UUIDs.timeBased(), "USER", deviceId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getDeviceService()).thenReturn(deviceService); when(deviceService.findDeviceByIdAsync(deviceId)).thenReturn(Futures.immediateFuture(device)); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java index 1b66433085..b61589d094 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNodeTest.java @@ -29,6 +29,9 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; +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.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.asset.AssetService; @@ -57,17 +60,24 @@ public class TbChangeOriginatorNodeTest { Asset asset = new Asset(); asset.setCustomerId(customerId); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}"); + RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); node.onMsg(ctx, msg); - ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx).tellNext(captor.capture()); - TbMsg actualMsg = captor.getValue(); - assertEquals(customerId, actualMsg.getOriginator()); - assertEquals(msg.getId(), actualMsg.getId()); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + + assertEquals(customerId, originatorCaptor.getValue()); } @Test @@ -78,17 +88,23 @@ public class TbChangeOriginatorNodeTest { Asset asset = new Asset(); asset.setCustomerId(customerId); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}"); + RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFuture(asset)); node.onMsg(ctx, msg); - ArgumentCaptor captor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx).spawn(captor.capture()); - TbMsg actualMsg = captor.getValue(); - assertEquals(customerId, actualMsg.getOriginator()); - assertEquals(msg.getId(), actualMsg.getId()); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + + assertEquals(customerId, originatorCaptor.getValue()); } @Test @@ -99,7 +115,10 @@ public class TbChangeOriginatorNodeTest { Asset asset = new Asset(); asset.setCustomerId(customerId); - TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}"); + RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + + TbMsg msg = new TbMsg(UUIDs.timeBased(), "ASSET", assetId, new TbMsgMetaData(), "{}", ruleChainId, ruleNodeId, 0L); when(ctx.getAssetService()).thenReturn(assetService); when(assetService.findAssetByIdAsync(assetId)).thenReturn(Futures.immediateFailedFuture(new IllegalStateException("wrong"))); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java index b904d7e95f..a29bc9055b 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -27,6 +27,8 @@ import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.mockito.stubbing.Answer; import org.thingsboard.rule.engine.api.*; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -56,8 +58,10 @@ public class TbTransformMsgNodeTest { metaData.putValue("temp", "7"); String rawJson = "{\"passed\": 5}"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); - TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}"); + RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L); + TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); when(scriptEngine.executeUpdate(msg)).thenReturn(transformedMsg); @@ -77,8 +81,10 @@ public class TbTransformMsgNodeTest { metaData.putValue("temp", "7"); String rawJson = "{\"passed\": 5"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); - TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}"); + RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L); + TbMsg transformedMsg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, "{new}", ruleChainId, ruleNodeId, 0L); mockJsExecutor(); when(scriptEngine.executeUpdate(msg)).thenReturn(transformedMsg); @@ -97,7 +103,9 @@ public class TbTransformMsgNodeTest { metaData.putValue("temp", "7"); String rawJson = "{\"passed\": 5"; - TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson); + RuleChainId ruleChainId = new RuleChainId(UUIDs.timeBased()); + RuleNodeId ruleNodeId = new RuleNodeId(UUIDs.timeBased()); + TbMsg msg = new TbMsg(UUIDs.timeBased(), "USER", null, metaData, rawJson, ruleChainId, ruleNodeId, 0L); mockJsExecutor(); when(scriptEngine.executeUpdate(msg)).thenThrow(new IllegalStateException("error")); diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index af07737559..d445aba641 100644 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -93,7 +93,7 @@ public class CoapTransportResource extends CoapResource { } else if (exchange.getRequestOptions().hasObserve()) { processExchangeGetRequest(exchange, featureType.get()); } else if (featureType.get() == FeatureType.ATTRIBUTES) { - processRequest(exchange, MsgType.GET_ATTRIBUTES_REQUEST); + processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); } else { log.trace("Invalid feature type parameter"); exchange.respond(ResponseCode.BAD_REQUEST); @@ -102,13 +102,13 @@ public class CoapTransportResource extends CoapResource { private void processExchangeGetRequest(CoapExchange exchange, FeatureType featureType) { boolean unsubscribe = exchange.getRequestOptions().getObserve() == 1; - MsgType msgType; + SessionMsgType sessionMsgType; if (featureType == FeatureType.RPC) { - msgType = unsubscribe ? MsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : MsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; + sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_RPC_COMMANDS_REQUEST : SessionMsgType.SUBSCRIBE_RPC_COMMANDS_REQUEST; } else { - msgType = unsubscribe ? MsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : MsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; + sessionMsgType = unsubscribe ? SessionMsgType.UNSUBSCRIBE_ATTRIBUTES_REQUEST : SessionMsgType.SUBSCRIBE_ATTRIBUTES_REQUEST; } - Optional sessionId = processRequest(exchange, msgType); + Optional sessionId = processRequest(exchange, sessionMsgType); if (sessionId.isPresent()) { if (exchange.getRequestOptions().getObserve() == 1) { exchange.respond(ResponseCode.VALID); @@ -125,24 +125,24 @@ public class CoapTransportResource extends CoapResource { } else { switch (featureType.get()) { case ATTRIBUTES: - processRequest(exchange, MsgType.POST_ATTRIBUTES_REQUEST); + processRequest(exchange, SessionMsgType.POST_ATTRIBUTES_REQUEST); break; case TELEMETRY: - processRequest(exchange, MsgType.POST_TELEMETRY_REQUEST); + processRequest(exchange, SessionMsgType.POST_TELEMETRY_REQUEST); break; case RPC: Optional requestId = getRequestId(exchange.advanced().getRequest()); if (requestId.isPresent()) { - processRequest(exchange, MsgType.TO_DEVICE_RPC_RESPONSE); + processRequest(exchange, SessionMsgType.TO_DEVICE_RPC_RESPONSE); } else { - processRequest(exchange, MsgType.TO_SERVER_RPC_REQUEST); + processRequest(exchange, SessionMsgType.TO_SERVER_RPC_REQUEST); } break; } } } - private Optional processRequest(CoapExchange exchange, MsgType type) { + private Optional processRequest(CoapExchange exchange, SessionMsgType type) { log.trace("Processing {}", exchange.advanced().getRequest()); exchange.accept(); Exchange advanced = exchange.advanced(); diff --git a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java index e3ef2ccd20..1c9631129f 100644 --- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -28,7 +28,7 @@ import org.thingsboard.server.common.msg.kv.AttributesKVMsg; import org.thingsboard.server.common.msg.session.AdaptorToSessionActorMsg; import org.thingsboard.server.common.msg.session.BasicAdaptorToSessionActorMsg; import org.thingsboard.server.common.msg.session.FromDeviceMsg; -import org.thingsboard.server.common.msg.session.MsgType; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.msg.session.SessionActorToAdaptorMsg; import org.thingsboard.server.common.msg.session.SessionContext; import org.thingsboard.server.common.msg.session.ToDeviceMsg; @@ -48,7 +48,7 @@ import org.thingsboard.server.transport.coap.session.CoapSessionCtx; public class JsonCoapAdaptor implements CoapTransportAdaptor { @Override - public AdaptorToSessionActorMsg convertToActorMsg(CoapSessionCtx ctx, MsgType type, Request inbound) throws AdaptorException { + public AdaptorToSessionActorMsg convertToActorMsg(CoapSessionCtx ctx, SessionMsgType type, Request inbound) throws AdaptorException { FromDeviceMsg msg = null; switch (type) { case POST_TELEMETRY_REQUEST: @@ -104,7 +104,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { @Override public Optional convertToAdaptorMsg(CoapSessionCtx ctx, SessionActorToAdaptorMsg source) throws AdaptorException { ToDeviceMsg msg = source.getMsg(); - switch (msg.getMsgType()) { + switch (msg.getSessionMsgType()) { case STATUS_CODE_RESPONSE: case TO_DEVICE_RPC_RESPONSE_ACK: return Optional.of(convertStatusCodeResponse((StatusCodeResponse) msg)); @@ -119,19 +119,19 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { case RULE_ENGINE_ERROR: return Optional.of(convertToRuleEngineErrorResponse(ctx, (RuleEngineErrorMsg) msg)); default: - log.warn("[{}] Unsupported msg type: {}!", source.getSessionId(), msg.getMsgType()); - throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + msg.getMsgType() + "!")); + log.warn("[{}] Unsupported msg type: {}!", source.getSessionId(), msg.getSessionMsgType()); + throw new AdaptorException(new IllegalArgumentException("Unsupported msg type: " + msg.getSessionMsgType() + "!")); } } private Response convertToRuleEngineErrorResponse(CoapSessionCtx ctx, RuleEngineErrorMsg msg) { ResponseCode status = ResponseCode.INTERNAL_SERVER_ERROR; switch (msg.getError()) { - case PLUGIN_TIMEOUT: + case QUEUE_PUT_TIMEOUT: status = ResponseCode.GATEWAY_TIMEOUT; break; default: - if (msg.getInMsgType() == MsgType.TO_SERVER_RPC_REQUEST) { + if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) { status = ResponseCode.BAD_REQUEST; } break; @@ -156,7 +156,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { return response; } - private UpdateAttributesRequest convertToUpdateAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { + private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, Request inbound) throws AdaptorException { String payload = validatePayload(ctx, inbound); try { return JsonConverter.convertToAttributes(new JsonParser().parse(payload)); diff --git a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java index fd0346ac38..df8545ebe4 100644 --- a/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java +++ b/transport/coap/src/test/java/org/thingsboard/server/transport/coap/CoapServerTest.java @@ -116,9 +116,9 @@ public class CoapServerTest { try { FromDeviceMsg deviceMsg = sessionMsg.getMsg(); ToDeviceMsg toDeviceMsg = null; - if (deviceMsg.getMsgType() == MsgType.POST_TELEMETRY_REQUEST) { + if (deviceMsg.getMsgType() == SessionMsgType.POST_TELEMETRY_REQUEST) { toDeviceMsg = BasicStatusCodeResponse.onSuccess(deviceMsg.getMsgType(), BasicRequest.DEFAULT_REQUEST_ID); - } else if (deviceMsg.getMsgType() == MsgType.GET_ATTRIBUTES_REQUEST) { + } else if (deviceMsg.getMsgType() == SessionMsgType.GET_ATTRIBUTES_REQUEST) { List data = new ArrayList<>(); data.add(new BaseAttributeKvEntry(new StringDataEntry("key1", "value1"), System.currentTimeMillis())); data.add(new BaseAttributeKvEntry(new LongDataEntry("key2", 42L), System.currentTimeMillis())); diff --git a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java b/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java index 743b3e777e..4732785de2 100644 --- a/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java +++ b/transport/http/src/main/java/org/thingsboard/server/transport/http/session/HttpSessionCtx.java @@ -57,7 +57,7 @@ public class HttpSessionCtx extends DeviceAwareSessionContext { @Override public void onMsg(SessionActorToAdaptorMsg source) throws SessionException { ToDeviceMsg msg = source.getMsg(); - switch (msg.getMsgType()) { + switch (msg.getSessionMsgType()) { case GET_ATTRIBUTES_RESPONSE: reply((GetAttributesResponse) msg); return; @@ -84,11 +84,11 @@ public class HttpSessionCtx extends DeviceAwareSessionContext { private void reply(RuleEngineErrorMsg msg) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; switch (msg.getError()) { - case PLUGIN_TIMEOUT: + case QUEUE_PUT_TIMEOUT: status = HttpStatus.REQUEST_TIMEOUT; break; default: - if (msg.getInMsgType() == MsgType.TO_SERVER_RPC_REQUEST) { + if (msg.getInSessionMsgType() == SessionMsgType.TO_SERVER_RPC_REQUEST) { status = HttpStatus.BAD_REQUEST; } break; diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 8d475a47cc..4fa32c6c56 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -54,7 +54,7 @@ import java.util.List; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.*; import static io.netty.handler.codec.mqtt.MqttMessageType.*; import static io.netty.handler.codec.mqtt.MqttQoS.*; -import static org.thingsboard.server.common.msg.session.MsgType.*; +import static org.thingsboard.server.common.msg.session.SessionMsgType.*; import static org.thingsboard.server.transport.mqtt.MqttTopics.*; /** diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 64df6bc148..f0b29cbd17 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -53,7 +53,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); @Override - public AdaptorToSessionActorMsg convertToActorMsg(DeviceSessionCtx ctx, MsgType type, MqttMessage inbound) throws AdaptorException { + public AdaptorToSessionActorMsg convertToActorMsg(DeviceSessionCtx ctx, SessionMsgType type, MqttMessage inbound) throws AdaptorException { FromDeviceMsg msg; switch (type) { case POST_TELEMETRY_REQUEST: @@ -94,7 +94,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { public Optional convertToAdaptorMsg(DeviceSessionCtx ctx, SessionActorToAdaptorMsg sessionMsg) throws AdaptorException { MqttMessage result = null; ToDeviceMsg msg = sessionMsg.getMsg(); - switch (msg.getMsgType()) { + switch (msg.getSessionMsgType()) { case STATUS_CODE_RESPONSE: case GET_ATTRIBUTES_RESPONSE: ResponseMsg responseMsg = (ResponseMsg) msg; @@ -134,12 +134,12 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { private MqttMessage convertResponseMsg(DeviceSessionCtx ctx, ToDeviceMsg msg, ResponseMsg responseMsg, Optional responseError) throws AdaptorException { MqttMessage result = null; - MsgType requestMsgType = responseMsg.getRequestMsgType(); + SessionMsgType requestMsgType = responseMsg.getRequestMsgType(); Integer requestId = responseMsg.getRequestId(); if (requestId >= 0) { - if (requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) { + if (requestMsgType == SessionMsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == SessionMsgType.POST_TELEMETRY_REQUEST) { result = MqttTransportHandler.createMqttPubAckMsg(requestId); - } else if (requestMsgType == MsgType.GET_ATTRIBUTES_REQUEST) { + } else if (requestMsgType == SessionMsgType.GET_ATTRIBUTES_REQUEST) { GetAttributesResponse response = (GetAttributesResponse) msg; Optional responseData = response.getData(); if (response.isSuccess() && responseData.isPresent()) { @@ -219,7 +219,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { } } - private UpdateAttributesRequest convertToUpdateAttributesRequest(SessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { + private AttributesUpdateRequest convertToUpdateAttributesRequest(SessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { String payload = validatePayload(ctx.getSessionId(), inbound.payload()); try { return JsonConverter.convertToAttributes(new JsonParser().parse(payload), inbound.variableHeader().messageId()); diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java index 632ab28999..6377fad69d 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java @@ -80,13 +80,13 @@ public class GatewayDeviceSessionCtx extends DeviceAwareSessionContext { private Optional getToDeviceMsg(SessionActorToAdaptorMsg sessionMsg) { ToDeviceMsg msg = sessionMsg.getMsg(); - switch (msg.getMsgType()) { + switch (msg.getSessionMsgType()) { case STATUS_CODE_RESPONSE: ResponseMsg responseMsg = (ResponseMsg) msg; if (responseMsg.isSuccess()) { - MsgType requestMsgType = responseMsg.getRequestMsgType(); + SessionMsgType requestMsgType = responseMsg.getRequestMsgType(); Integer requestId = responseMsg.getRequestId(); - if (requestId >= 0 && requestMsgType == MsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == MsgType.POST_TELEMETRY_REQUEST) { + if (requestId >= 0 && requestMsgType == SessionMsgType.POST_ATTRIBUTES_REQUEST || requestMsgType == SessionMsgType.POST_TELEMETRY_REQUEST) { return Optional.of(MqttTransportHandler.createMqttPubAckMsg(requestId)); } } diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java index b4dd8db9c1..f35434aaaa 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java @@ -179,7 +179,7 @@ public class GatewaySessionCtx { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + json); } long ts = System.currentTimeMillis(); - BasicUpdateAttributesRequest request = new BasicUpdateAttributesRequest(requestId); + BasicAttributesUpdateRequest request = new BasicAttributesUpdateRequest(requestId); JsonObject deviceData = deviceEntry.getValue().getAsJsonObject(); request.add(JsonConverter.parseValues(deviceData).stream().map(kv -> new BaseAttributeKvEntry(kv, ts)).collect(Collectors.toList())); GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js index 03b3a82684..1b417ddd3c 100644 --- a/ui/src/app/api/rule-chain.service.js +++ b/ui/src/app/api/rule-chain.service.js @@ -184,6 +184,15 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co ruleNodeComponents.push( types.ruleChainNodeComponent ); + ruleNodeComponents.sort( + (comp1, comp2) => { + var result = comp1.type.localeCompare(comp2.type); + if (result == 0) { + result = comp1.name.localeCompare(comp2.name); + } + return result; + } + ); deferred.resolve(ruleNodeComponents); }, () => { diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js index 88c6353401..f04b0361ad 100644 --- a/ui/src/app/import-export/import-export.service.js +++ b/ui/src/app/import-export/import-export.service.js @@ -316,6 +316,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document, delete ruleChainMetaData.ruleChainId; for (var i=0;i